JavaScript array .reduce with async/await
The problem is that your accumulator values are promises - they're return values of async function
s. To get sequential evaluation (and all but the last iteration to be awaited at all), you need to use
const data = await array.reduce(async (accumP, current, index) => { const accum = await accumP; …}, Promise.resolve(…));
That said, for async
/await
I would in general recommend to use plain loops instead of array iteration methods, they're more performant and often simpler.
I like Bergi's answer, I think it's the right way to go.
I'd also like to mention a library of mine, called Awaity.js
Which lets you effortlessly use functions like reduce
, map
& filter
with 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 = await res.json(); return { ...posts, [id]: post };}, {})posts // { 1: { ... }, 2: { ... }, 3: { ... } }
[Not addressing OPs exact prob; focused on others who land here.]
Reduce is commonly used when you need the result of the previous steps before you can process the next. In that case, you can string promises together a la:
promise = elts.reduce( async (promise, elt) => { return promise.then(async last => { return await f(last, elt) }) }, Promise.resolve(0)) // or "" or [] or ...
Here's an example with uses fs.promise.mkdir() (sure, much simpler to use mkdirSync, but in my case, it's across a network):
const Path = require('path')const Fs = require('fs')async function mkdirs (path) { return path.split(/\//).filter(d => !!d).reduce( async (promise, dir) => { return promise.then(async parent => { const ret = Path.join(parent, dir); try { await Fs.promises.lstat(ret) } catch (e) { console.log(`mkdir(${ret})`) await Fs.promises.mkdir(ret) } return ret }) }, Promise.resolve(""))}mkdirs('dir1/dir2/dir3')
Below is another example which add 100 + 200 ... 500 and waits around a bit:
async function slowCounter () { const ret = await ([100, 200, 300, 400, 500]).reduce( async (promise, wait, idx) => { return promise.then(async last => { const ret = last + wait console.log(`${idx}: waiting ${wait}ms to return ${ret}`) await new Promise((res, rej) => setTimeout(res, wait)) return ret }) }, Promise.resolve(0)) console.log(ret)}slowCounter ()