wake-up-neo.com

Node JS Promise.all und forEach

Ich habe eine Array-ähnliche Struktur, die asynchrone Methoden verfügbar macht. Die asynchrone Methode ruft Array-Strukturen auf, die wiederum mehr asynchrone Methoden verfügbar machen. Ich erstelle ein weiteres JSON-Objekt zum Speichern von Werten, die aus dieser Struktur abgerufen wurden, und muss daher die Referenzen in Rückrufen im Auge behalten.

Ich habe eine Brute-Force-Lösung codiert, möchte jedoch eine idiomatischere oder sauberere Lösung kennenlernen.

  1. Das Muster sollte für n Verschachtelungsebenen wiederholbar sein.
  2. Ich muss promise.all oder eine ähnliche Technik verwenden, um festzustellen, wann die beiliegende Routine zu lösen ist.
  3. Nicht bei jedem Element muss ein asynchroner Aufruf erfolgen. In einem geschachtelten Versprechen kann ich also nicht einfach Zuweisungen zu meinen JSON-Array-Elementen basierend auf dem Index vornehmen. Trotzdem muss ich so etwas wie promise.all im verschachtelten forEach verwenden, um sicherzustellen, dass alle Eigenschaftszuweisungen vorgenommen wurden, bevor die umschließende Routine aufgelöst wird.
  4. Ich benutze das Bluebird-Versprechen lib, aber dies ist keine Voraussetzung

Hier ist ein Teilcode -

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

Es ist ziemlich einfach mit ein paar einfachen Regeln:

  • Wenn Sie ein Versprechen in einem then erstellen, geben Sie es zurück. - Jedes Versprechen, das Sie nicht zurückgeben, wird nicht im Freien abgewartet.
  • Immer wenn Sie mehrere Versprechen erstellen, .all them - auf diese Weise wartet es auf alle Versprechungen und es werden keine Fehler von ihnen zum Schweigen gebracht.
  • Wann immer Sie thens verschachteln, können Sie normalerweise in der Mitte zurückkehren - then Ketten sind normalerweise höchstens 1 Ebene tief.
  • Wann immer Sie IO ausführen, sollte es mit einem Versprechen sein - entweder sollte es ein Versprechen sein oder es sollte ein Versprechen verwenden, um seinen Abschluss zu signalisieren.

Und ein paar Tipps:

  • Mapping ist besser mit .map als mit for/Push - Wenn Sie Werte mit einer Funktion zuordnen, können Sie mit map die Idee, Aktionen einzeln anzuwenden und die Ergebnisse zu aggregieren, präzise ausdrücken.
  • Parallelität ist besser als sequentielle Ausführung, wenn sie frei ist - Es ist besser, Dinge gleichzeitig auszuführen und auf sie zu warten Promise.all als Dinge nacheinander auszuführen - jedes wartet vor dem nächsten.

Ok, also fangen wir an:

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

Hier ist ein einfaches Beispiel für die Verwendung von redu. Es wird seriell ausgeführt, behält die Einfügereihenfolge bei und benötigt kein 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());
}

Und benutze es so:

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

Es hat sich als nützlich erwiesen, einen optionalen Kontext in die Schleife zu senden. Der Kontext ist optional und wird von allen Iterationen gemeinsam verwendet.

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

Ihre Versprechen-Funktion würde so aussehen:

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

Ich hatte durch die gleiche Situation. Ich habe mit zwei Promise.All () gelöst.

Ich denke, es war eine wirklich gute Lösung, also habe ich sie auf npm veröffentlicht: https://www.npmjs.com/package/promise-foreach

Ich denke, Ihr Code wird so etwas sein

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