0

I'm trying to traverse through results from ElasticSearch and modify the original object by replacing items in an array. I am almost through, but the Promise is resolved more than once, leaving me with no usable result.

I'm using source.included = Array.map() to replace the original array with the results of the function call and it works almost. It's important to keep the original structure of the object and the order within the included array, that's why I decided to try this approach. I tried to orient myself on this example, but Promise.all() somehow won't resolve the promises. Any ideas?

let source = {
  title: '1',
  included: [{
    _id: 'new',
    _type: 'mytype'
  }]
}

function recursiveGet (source) {
  return new Promise((resolve, reject) => {
    client.mget({
      body: {
        docs: docsToFetch
      }
    })
    .then(response => {
      return source.included.map(item => {
        return somethingAsync(response)
      })
    })
    .then(promises => {
      Promise.all(promises)
        .then(resolved => {
          source.included = resolved.map(doc => {
            if (doc.included && !doc.resolved) {
              resolve(recursiveGet(doc))
            } else {
              resolve(doc)
            }
          })
        })
    })
  })
}

recursiveGet(source)
Patrick
  • 7,903
  • 11
  • 52
  • 87
  • 2
    *"but the Promise is resolved more than once"* No, it isn't. Promises by definition are only resolved (technically, *settled*) once. – T.J. Crowder Jun 18 '16 at 17:30
  • I only have done this with $q in Angular. The trick was to pass the deferred object (the promise) to your recursive function. – user3791775 Jun 18 '16 at 17:33
  • 3
    Please provide **exact input, required output and specific problem**. – Amit Jun 18 '16 at 17:42
  • _"but Promise.all() somehow won't resolve the promises."_ What do you mean? You do not `return` `Promise.all()` from last `.then()`? – guest271314 Jun 18 '16 at 17:48

1 Answers1

1

Avoid the Promise constructor antipattern!

You cannot (must not) call the resolve multiple times - in your case, from within a map loop. Use this instead:

function recursiveGet (source) {
  return client.mget({
    body: {
      docs: docsToFetch
    }
  }).then(response => {
    let promises = source.included.map(item => {
      return somethingAsync(response) // did you mean `item` here?
    })
    return Promise.all(promises);
  })
  .then(resolved => {
    let morePromises = resolved.map(doc => {
//                              ^^^ another loop...
      if (doc.included && !doc.resolved) {
        return recursiveGet(doc);
      } else {
        return doc;
      }
    });
    return Promise.all(morePromises).then(moreResults => {
//         ^^^^^^^^^^^ another Promise.all!
      source.included = moreResults;
      return source;
    });
  });
}

If you don't want two sequential loops, you can also chain each of the result processing callbacks to the somethingAsync promise directly.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Brilliant, thanks! Needed a resolve() in the last .then() and therefore keep the anti-pattern, but it works. – Patrick Jun 19 '16 at 14:15
  • @Patrick: No, you absolutely should not keep the antipattern. – Bergi Jun 19 '16 at 14:19
  • I've tried a couple of ways to remove it, but if there's no resolve() in the final then(), the Promise won't resolve. – Patrick Jun 19 '16 at 20:31
  • @Patrick: You should completely omit the `new Promise` call, and `return` the return value of the final `then()` call. There is no need to call `resolve` when there is no `resolve` callback around. Have a closer look at the begin of the code in my answer again. – Bergi Jun 19 '16 at 22:07
  • That's what I did .. Return the client.mget and return source in the final then, still, the Promise doesn't get back to the main code. I'll go through and try a few more things, but it's a great start to have it working. Was becoming desperate with this. – Patrick Jun 20 '16 at 08:39