Node JS Promise.all and forEach

I have an array similar to a structure that provides async methods. The async method calls inverse array structures, which in turn expose more asynchronous methods. I am creating another JSON object to store the values ​​obtained from this structure, and therefore I need to be careful in tracking links in callbacks.

I encoded a brute force solution, but I would like to know a more idiomatic or pure solution.

  • The pattern should be repeated for n levels of nesting.
  • I need to use prom.all or some similar method to determine when to allow the attached procedure.
  • Not every element will necessarily include an asynchronous call. Thus, a nested promise. I can't just assign my elements to a JSON array based on an index. However, I need to use something like prom.all in a nested forEach, to ensure that all property assignments have been made before resolving the attached procedure.
  • I use the vibbbbb promise, but this is not a requirement

Here is some partial code -

var jsonItems = []; items.forEach(function(item){ var jsonItem = {}; jsonItem.name = item.name; item.getThings().then(function(things){ // or Promise.all(allItemGetThingCalls, function(things){ things.forEach(function(thing, index){ jsonItems[index].thingName = thing.name; if(thing.type === 'file'){ thing.getFile().then(function(file){ //or promise.all? jsonItems[index].filesize = file.getSize(); 
+103
javascript promise asynchronous
Jul 14 '15 at 17:46
source share
3 answers

This is pretty simple with some simple rules:

  • Whenever you make a promise in then , return it β€” any promise that you do not return will not wait outside.
  • Whenever you create several promises, .all them - so he expects that all promises and none of them are disabled.
  • Whenever you are in then s nest, you can usually return in the middle - then . Chains are usually no more than 1 level.
  • Whenever you perform an IO, it should be with a promise β€” either it should be promised, or it should use a promise to report its completion.

And some tips:

  • Mapping is better done with .map than with for/push - if you map values ​​using a function, map allows you to briefly express the concept of applying actions one after another and aggregating the results.
  • Concurrency is better than sequential execution, if it is free - it is better to perform actions at the same time and wait for them to Promise.all than to do things one by one - each of them waits until the next.

So let's get started:

 var items = [1, 2, 3, 4, 5]; var fn = function asyncMultiplyBy2(v){ // sample async action return new Promise(resolve => setTimeout(() => resolve(v * 2), 100)); }; // map over forEach since it returns var actions = items.map(fn); // run the function over all items // we now have a promises array and we want to wait for it var results = Promise.all(actions); // pass array of promises results.then(data => // or just .then(console.log) console.log(data) // [2, 4, 6, 8, 10] ); // we can nest this of course, as I said, `then` chains: var res2 = Promise.all([1, 2, 3, 4, 5].map(fn)).then( data => Promise.all(data.map(fn)) ).then(function(data){ // the next `then` is executed after the promise has returned from the previous // `then` fulfilled, in this case it an aggregate promise because of // the `.all` return Promise.all(data.map(fn)); }).then(function(data){ // just for good measure return Promise.all(data.map(fn)); }); // now to get the results: res2.then(function(data){ console.log(data); // [16, 32, 48, 64, 80] }); 
+328
Jul 14 '15 at 18:27
source share

Here is a simple example using abbreviation. It works in serial mode, supports insertion order and does not require Bluebird.

 /** * * @param items An array of items. * @param fn A function that accepts an item from the array and returns a promise. * @returns {Promise} */ function forEachPromise(items, fn) { return items.reduce(function (promise, item) { return promise.then(function () { return fn(item); }); }, Promise.resolve()); } 

And use it as follows:

 var items = ['a', 'b', 'c']; function logItem(item) { return new Promise((resolve, reject) => { process.nextTick(() => { console.log(item); resolve(); }) }); } forEachPromise(items, logItem).then(() => { console.log('done'); }); 

We found it useful to send an optional context to the loop. Context is optional and shared by all iterations.

 function forEachPromise(items, fn, context) { return items.reduce(function (promise, item) { return promise.then(function () { return fn(item, context); }); }, Promise.resolve()); } 

Your promise function will look like this:

 function logItem(item, context) { return new Promise((resolve, reject) => { process.nextTick(() => { console.log(item); context.itemCount++; resolve(); }) }); } 
+36
Jan 22 '17 at 12:57
source share

I had the same situation. I decided to use two Promise.All ().

I think this is a really good solution, so I posted it on npm: https://www.npmjs.com/package/promise-foreach

I think your code will be something like this

 var promiseForeach = require('promise-foreach') var jsonItems = []; promiseForeach.each(jsonItems, [function (jsonItems){ return new Promise(function(resolve, reject){ if(jsonItems.type === 'file'){ jsonItems.getFile().then(function(file){ //or promise.all? resolve(file.getSize()) }) } }) }], function (result, current) { return { type: current.type, size: jsonItems.result[0] } }, function (err, newList) { if (err) { console.error(err) return; } console.log('new jsonItems : ', newList) }) 
+1
Jun 23 '17 at 22:18
source share



All Articles