Node JS Promise.all e forEach

Ho una matrice come struttura che espone metodi asincroni. Il metodo async chiama le strutture di array di restituzione che a loro volta espongono metodi più asincroni. Sto creando un altro object JSON per memorizzare i valori ottenuti da questa struttura e quindi devo fare attenzione a tenere traccia dei riferimenti nei callback.

Ho codificato una soluzione di forza bruta, ma mi piacerebbe imparare una soluzione più idiomatica o pulita.

  1. Il modello dovrebbe essere ripetibile per n livelli di nidificazione.
  2. Ho bisogno di usare promise.all o qualche tecnica simile per determinare quando risolvere la routine di chiusura.
  3. Non tutti gli elementi implicano necessariamente effettuare una chiamata asincrona. Quindi in una promise nidificata. Tutto ciò che posso fare non è semplicemente creare assegnazioni ai miei elementi di array JSON in base all’indice. Ciononostante, ho bisogno di usare qualcosa come promise.all nel nest annegato per assicurare che tutte le assegnazioni di proprietà siano state fatte prima di risolvere la routine di chiusura.
  4. Sto usando il bluebird promise lib ma questo non è un requisito

Ecco un codice parziale –

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(); 

È piuttosto semplice con alcune semplici regole:

  • Ogni volta che crei una promise in un then , restituiscilo : qualsiasi promise che non torni non sarà attesa fuori.
  • Ogni volta che crei più promesse, tutte quante , in questo modo si attendono tutte le promesse e nessun errore da parte di nessuno di essi viene messo a tacere.
  • Ogni volta che nidifichi then s, in genere puoi tornare al centrothen catene sono solitamente al massimo a 1 livello di profondità.
  • Ogni volta che esegui l’IO, dovrebbe essere una promise : o dovrebbe essere in una promise o dovrebbe usare una promise per segnalare il suo completamento.

E alcuni consigli:

  • La mapping è fatta meglio con .map che con for/push – se stai mappando i valori con una funzione, map ti consente di esprimere in modo conciso la nozione di applicare le azioni una ad una e di aggregare i risultati.
  • La concorrenza è migliore dell’esecuzione sequenziale se è gratuita – è meglio eseguire le cose contemporaneamente e attendere Promise.all che eseguire le cose una dopo l’altra – ciascuna in attesa prima della successiva.

Ok, allora iniziamo:

 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's 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] }); 

Ecco un semplice esempio usando ridurre. Funziona in serie, mantiene l’ordine di inserimento e non richiede 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()); } 

E usalo in questo modo:

 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'); }); 

Abbiamo trovato utile inviare un contesto opzionale in loop. Il contesto è facoltativo e condiviso da tutte le iterazioni.

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

La tua funzione di promise sarebbe simile a questa:

 function logItem(item, context) { return new Promise((resolve, reject) => { process.nextTick(() => { console.log(item); context.itemCount++; resolve(); }) }); } 

Ho avuto la stessa situazione. Ho risolto utilizzando due Promise.All ().

Penso che sia stata una buona soluzione, quindi l’ho pubblicata su npm: https://www.npmjs.com/package/promise-foreach

Penso che il tuo codice sarà qualcosa del genere

 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) })