Patrick Patrick - 5 months ago 10
Node.js Question

Recursively modifying an object with Promises

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)

Answer

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.

Comments