2016-12-20 1 views
12

Scheinen einige Probleme Einbeziehung async/warten mit .reduce() zu haben, etwa so:JavaScript-Array .reduce mit Asynchron/await

const data = await bodies.reduce(async(accum, current, index) => { 
    const methodName = methods[index] 
    const method = this[methodName] 
    if (methodName == 'foo') { 
    current.cover = await this.store(current.cover, id) 
    console.log(current) 
    return { 
     ...accum, 
     ...current 
    } 
    } 
    return { 
    ...accum, 
    ...method(current.data) 
    } 
}, {}) 
console.log(data) 

Das data Objekt wird protokolliert vor die this.store abgeschlossen ist .. .

Ich weiß, dass Sie Promise.all mit async Schleifen verwenden können, aber gilt das für .reduce()?

Antwort

21

Das Problem ist, dass Ihre Akku-Werte Versprechen sind - sie sind Rückgabewerte von async function s. Um sequentielle Auswertung zu erhalten (und alle, aber die letzte Iteration überhaupt abgewartet werden), müssen Sie

const data = await array.reduce(async (accumP, current, index) => { 
    const accum = await accumP; 
    … 
}, Promise.resolve(…)); 

verwenden, das heißt, für async/await würde ich in der Regel empfehlen use plain loops instead of array iteration methods, sie sind leistungsfähigere und oft einfacher.

+1

Vielen Dank für Ihre Beratung am Ende . Ich benutzte nur eine einfache for-Schleife für das, was ich tat, und es waren die gleichen Codezeilen, aber viel einfacher zu lesen ... –

+0

Der 'initialValue' von' reduce' muss kein 'Versprechen sein ', es wird jedoch in den meisten Fällen die Absicht klären. – EECOLOR

+0

@EECOLOR Es sollte aber sein. Ich mag es wirklich nicht, auf ein Versprechen warten zu müssen – Bergi

1

Sie können Ihre gesamte Map/Iteratorblöcke in ihre eigene Promise.resolve einbinden und darauf warten, dass sie abgeschlossen ist. Das Problem besteht jedoch darin, dass der Akkumulator nicht die resultierenden Daten/Objekte enthält, die Sie bei jeder Iteration erwarten würden. Aufgrund der internen async/await/Promise-Kette wird der Akku tatsächlich Versprechen selbst, die wahrscheinlich noch zu lösen haben, trotz der Verwendung eines erwarten Schlüsselwort vor Ihrem Anruf in den Laden (die Sie glauben lassen, dass die Iteration wird nicht wirklich zurück, bis der Anruf beendet und der Speicher aktualisiert wird.

dies ist zwar nicht die eleganteste Lösung ist, können Sie eine Option haben, ist Ihre Daten Objektvariable außerhalb des Gültigkeitsbereichs zu bewegen, und weisen Sie es als ein lassen, so dass Es kann zu einer ordnungsgemäßen Bindung und Mutation kommen.Dann aktualisieren Sie dieses Datenobjekt von innerhalb Ihres Iterators, wenn die async/await/Promise-Aufrufe aufgelöst werden:

/* allow the result object to be initialized outside of scope 
    rather than trying to spread results into your accumulator on iterations, 
    else your results will not be maintained as expected within the 
    internal async/await/Promise chain. 
*/  
let data = {}; 

await Promise.resolve(bodies.reduce(async(accum, current, index) => { 
    const methodName = methods[index] 
    const method = this[methodName]; 
    if (methodName == 'foo') { 
    // note: this extra Promise.resolve may not be entirely necessary 
    const cover = await Promise.resolve(this.store(current.cover, id)); 
    current.cover = cover; 
    console.log(current); 
    data = { 
     ...data, 
     ...current, 
    }; 
    return data; 
    } 
    data = { 
    ...data, 
    ...method(current.data) 
    }; 
    return data; 
}, {}); 
console.log(data); 
0

Ich mag Bergis Antwort, ich denke, es ist der richtige Weg.

ich auch eine Bibliothek von mir erwähnen möchte, genannt Awaity.js

Welche Sie mühelos Funktionen wie reduce verwenden können, map & filter mit async/await:

import reduce from 'awaity/reduce'; 

const posts = await reduce([1,2,3], async (posts, id) => { 
    const res = await fetch('/api/posts/' + id); 
    const post = res.json(); 

    return { 
    ...posts, 
    [id]: post 
    } 
}, {}) 

posts // { 1: { ... }, 2: { ... }, 3: { ... } }