web-dev-qa-db-fra.com

Node JS Promise.all et forEach

J'ai un tableau comme la structure qui expose les méthodes asynchrones. Les appels de méthodes asynchrones renvoient des structures de tableau qui, à leur tour, exposent davantage de méthodes asynchrones. Je suis en train de créer un autre objet JSON pour stocker les valeurs obtenues à partir de cette structure. Je dois donc veiller à ne pas perdre la trace des références dans les rappels.

J'ai codé une solution de force brute, mais j'aimerais apprendre une solution plus idiomatique ou plus propre.

  1. Le motif doit être reproductible pour n niveaux d'imbrication.
  2. Je dois utiliser promise.all ou une technique similaire pour déterminer quand résoudre la routine englobante.
  3. Tous les éléments n'impliqueront pas nécessairement un appel asynchrone. Donc, dans une promesse imbriquée, je ne peux pas simplement attribuer des éléments à mon tableau JSON en fonction de l’index. Néanmoins, je dois utiliser quelque chose comme promesse.all dans le for imbriqué pour garantir que toutes les affectations de propriétés ont été effectuées avant la résolution de la routine englobante.
  4. J'utilise la promesse bluebird lib mais ce n'est pas une obligation

Voici un code partiel -

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

C'est assez simple avec quelques règles simples:

  • Chaque fois que vous créez une promesse dans un then, renvoyez-la - aucune promesse que vous ne revenez pas ne sera pas attendue à l'extérieur.
  • Chaque fois que vous créez plusieurs promesses, .all leur - ainsi, toutes les promesses seront attendues et aucune erreur de l'une d'entre elles ne sera réduite au silence.
  • Chaque fois que vous imbriquez thens, vous pouvez généralement revenir au milie - then les chaînes ont généralement une profondeur maximale de 1 niveau.
  • Chaque fois que vous effectuez une E/S, cela devrait être avec une promesse - soit cela devrait être dans une promesse, soit il devrait utiliser une promesse pour signaler son achèvement.

Et quelques astuces:

  • Le mappage est mieux réalisé avec .map que avec for/Push - si vous mappez des valeurs avec une fonction, map vous permet d'exprimer de manière concise la notion d'appliquer des actions par un et agréger les résultats.
  • La simultanéité est meilleure que l'exécution séquentielle si elle est gratuite - il vaut mieux exécuter les choses simultanément et les attendre Promise.all plutôt que d'exécuter les choses les unes après les autres - chaque attente avant la suivante.

Ok, alors commençons:

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]
});
321
Benjamin Gruenbaum

Voici un exemple simple en utilisant réduire. Il fonctionne en série, maintient l'ordre d'insertion et ne requiert pas 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());
}

Et utilisez-le comme ceci:

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

Nous avons trouvé utile d'envoyer un contexte optionnel dans loop. Le contexte est optionnel et partagé par toutes les itérations.

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

Votre fonction de promesse ressemblerait à ceci:

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

J'ai traversé la même situation. J'ai résolu en utilisant deux Promise.All ().

Je pense que c’était vraiment une bonne solution, je l’ai donc publiée sur npm: https://www.npmjs.com/package/promise-foreach

Je pense que votre code sera quelque chose comme ça

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
saulsluz