JavaScript数组.reduce与async/await

ben*_*e89 43 javascript reduce promise async-await ecmascript-next

似乎有一些问题将async/await与.reduce()结合起来,如下所示:

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)
Run Code Online (Sandbox Code Playgroud)

data对象被记录之前this.store完成...

我知道你可以使用Promise.all异步循环,但这适用于.reduce()

Ber*_*rgi 77

问题是你的累加器值是promises - 它们是async functions的返回值.要获得顺序评估(除了最后一次迭代之外的所有迭代),您需要使用

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

也就是说,对于async/ await我一般会建议使用普通循环而不是数组迭代方法,它们性能更高,而且通常更简单.

  • `reduce` 的 `initialValue` 不需要是 `Promise`,但是在大多数情况下它会阐明意图。 (4认同)
  • 最后谢谢你的建议。我最终只使用了一个普通的 for 循环来完成我正在做的事情,它是相同的代码行,但更容易阅读...... (3认同)
  • @EECOLOR 并且在使用 TypeScript 时,初始值需要是一个 promise,因为回调的返回类型必须始终匹配累加器的类型。 (2认同)

inw*_*sel 10

当前接受的答案建议使用Promise.all()而不是async reduce. 然而,这与 an 的行为不同,async reduce并且仅与您希望异常立即停止所有迭代的情况相关,但情况并非总是如此。

此外,在该答案的评论中,建议您应该始终等待累加器作为减速器中的第一个语句,因为否则您可能会面临未处理的承诺拒绝的风险。发帖者还说这是OP所要求的,但事实并非如此。相反,他只是想知道一切何时完成。为了知道您确实需要这样做await acc,但这可能是在减速器中的任何一点。

const reducer = async(acc, key) => {
  const response = await api(item);

  return {
    ...await acc, // <-- this would work just as well for OP
    [key]: response,
  }
}
const result = await ['a', 'b', 'c', 'd'].reduce(reducer, {});
console.log(result); // <-- Will be the final result
Run Code Online (Sandbox Code Playgroud)

如何安全使用asyncreduce

话虽这么说,以这种方式使用减速器确实意味着您需要保证它不会抛出,否则您将得到“未处理的承诺拒绝”。完全可以通过使用try-catch, 块catch返回累加器(可选地包含失败的 API 调用的记录)来确保这一点。

const reducer = async (acc, key) => {
    try {
        data = await doSlowTask(key);
        return {...await acc, [key]: data};
    } catch (error) {
        return {...await acc, [key]: {error}};
    };
}
const result = await ['a', 'b', 'c','d'].reduce(reducer, {});
Run Code Online (Sandbox Code Playgroud)

与 的区别Promise.allSettled 您可以通过使用 来接近 an 的行为async reduce(带有错误捕获)Promise.allSettled。然而,这使用起来很笨拙:如果你想减少到一个对象,你需要在它后面添加另一个同步减少。

Promise.allSettled+ 常规 的理论时间复杂度也更高reduce,尽管可能很少有用例会产生影响。async reduce可以从第一项完成的那一刻开始累积,而reduceafterPromise.allSettled会被阻塞,直到所有的承诺都得到履行。当循环大量元素时,这可能会产生影响。

const reducer = async(acc, key) => {
  const response = await api(item);

  return {
    ...await acc, // <-- this would work just as well for OP
    [key]: response,
  }
}
const result = await ['a', 'b', 'c', 'd'].reduce(reducer, {});
console.log(result); // <-- Will be the final result
Run Code Online (Sandbox Code Playgroud)

使用以下命令检查执行顺序Promise.allSettled

const reducer = async (acc, key) => {
    try {
        data = await doSlowTask(key);
        return {...await acc, [key]: data};
    } catch (error) {
        return {...await acc, [key]: {error}};
    };
}
const result = await ['a', 'b', 'c','d'].reduce(reducer, {});
Run Code Online (Sandbox Code Playgroud)


eri*_*icP 9

[未解决 OP 的确切问题;专注于降落在这里的其他人。]

当您需要前面步骤的结果才能处理下一步时,通常会使用Reduce。在这种情况下,你可以将 Promise 串在一起:

promise = elts.reduce(
    async (promise, elt) => {
        return promise.then(async last => {
            return await f(last, elt)
        })
    }, Promise.resolve(0)) // or "" or [] or ...
Run Code Online (Sandbox Code Playgroud)

这是一个使用 fs.promise.mkdir() 的示例(当然,使用 mkdirSync 更简单,但就我而言,它是跨网络的):

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')
Run Code Online (Sandbox Code Playgroud)

下面是另一个例子,添加 100 + 200 ... 500 并稍等一下:

promise = elts.reduce(
    async (promise, elt) => {
        return promise.then(async last => {
            return await f(last, elt)
        })
    }, Promise.resolve(0)) // or "" or [] or ...
Run Code Online (Sandbox Code Playgroud)


Asa*_*atz 6

我喜欢贝尔吉的回答,我认为这是正确的方法。

我还想提一下我的一个库,叫做Awaity.js

它可以让你毫不费力的使用功能,如reducemapfilter具有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: { ... } }
Run Code Online (Sandbox Code Playgroud)

  • 顺序的,因为每次迭代都取决于前一次迭代的返回值 (2认同)