map() function with async/await



有很多关于 async/await 在 javascript map 函数中的行为的主题,但仍然,下面两个例子的详细解释会很好:

const resultsPromises = myArray.map(async number => {
return await getResult(number);
});
const resultsPromises = myArray.map(number => {
return getResult(number);
});

编辑:这当然是一个虚构的案例,所以刚刚开始辩论,为什么,如何以及何时应该映射功能等待await关键字。 解决方案 如何修改此示例,调用 Promise.all() 不是这个问题的目的。
getResult是一个异步函数

其他答案很好地涵盖了您的示例行为的细节,但我想尝试更简洁地陈述它。

const resultsPromises = myArray.map(async number => {
return await getResult(number);
});
const resultsPromises = myArray.map(number => {
return getResult(number);
});
  1. Array.prototype.map同步循环遍历数组,并将每个元素转换为其回调的返回值。

  2. 这两个示例都返回一个Promise

    • async函数始终返回一个Promise

    • getResult返回一个Promise

    • 因此,如果没有错误,您可以在伪代码中将它们视为:

const resultsPromises = myArray.map(/* map each element to a Promise */);
  1. 正如zero298所述,alnitak所证明的那样,这非常迅速(同步)按顺序开始每个承诺;但是,由于它们是并行运行的,因此每个承诺将按照他们认为合适的方式解决/拒绝,并且可能不会按顺序解决(履行或拒绝)。

  2. 要么并行运行承诺并使用Promise.all收集结果,要么使用 for * 循环或Array.prototype.reduce按顺序运行它们。

或者,您可以使用第三方模块来执行我维护的可链接异步 JavaScript 方法来清理内容,并且 - 也许 - 使代码符合您对异步映射操作如何工作的直觉:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const getResult = async n => {
await delay(Math.random() * 1000);
console.log(n);
return n;
};
(async () => {
console.log('parallel:');
await AsyncAF([1, 2, 3]).map(getResult).then(console.log);

console.log('sequential:');
await AsyncAF([1, 2, 3]).series.map(getResult).then(console.log)
})();
<script src="https://unpkg.com/async-af@7.0.12/index.js"></script>

当您想要通过删除.then()回调来展平代码或想要隐式返回 Promise 时,async/await很有用:

const delay = n => new Promise(res => setTimeout(res, n));
async function test1() {
await delay(200);
// do something usefull here
console.log('hello 1');
}
async function test2() {
return 'hello 2'; // this returned value will be wrapped in a Promise
}
test1();
test2().then(console.log);

但是,在您的情况下,您没有使用await来替换.then(),也没有使用它来返回隐式 Promise,因为您的函数已经返回了一个 Promise。所以它们不是必需的。

并行执行所有承诺

如果你想并行运行所有承诺,我建议简单地返回map()getResult的结果并生成一个承诺数组。承诺将按顺序启动,但最终将并行运行。

const resultsPromises = indicators.map(getResult);

然后,您可以等待所有承诺并使用Promise.all()获得解决的结果:

const data = [1, 2, 3];
const getResult = x => new Promise(res => {
return setTimeout(() => {
console.log(x);
res(x);
}, Math.random() * 1000)
});
Promise.all(data.map(getResult)).then(console.log);

承诺的顺序执行

但是,如果你想按顺序运行每个承诺,并在运行下一个承诺之前等待上一个承诺解析,那么你可以使用 reduce() 和像这样async/await

const data = [1, 2, 3];
const getResult = x => new Promise(res => {
return setTimeout(() => {
console.log(x);
res(x);
}, Math.random() * 1000)
});
data.reduce(async (previous, x) => {
const result = await previous;
return [...result, await getResult(x)];
}, Promise.resolve([])).then(console.log);

Array.prototype.map()

是一个转换数组的函数。 它将一个数组映射到另一个数组。 其函数签名最重要的部分是回调。 回调是对数组中的每个项目调用的,该回调返回的内容是放入map返回的新数组中的内容。

它不会对返回的内容执行任何特殊操作。 它不会对项目调用.then(),也不会await任何东西。 它同步转换数据。

这意味着,如果回调返回一个Promise(所有async函数都这样做),所有的承诺都将是"热"的并并行运行。

在您的示例中,如果getResult()返回Promise或本身是异步的,则您的实现之间实际上没有区别。resultsPromises将由可能尚未解决或可能尚未解决的Promise填充。

如果要等待所有操作完成再继续,则需要使用Promise.all().

此外,如果一次只希望运行 1 个getResults(),请使用常规for循环并在循环中await

如果第一个代码片段的目的是有一个.map调用,该调用在返回之前等待所有承诺被解析(并让这些回调按顺序运行),恐怕它不会那样工作。.map函数不知道如何使用async函数执行此操作。

这可以通过以下代码进行演示:

const array = [ 1, 2, 3, 4, 5 ];

function getResult(n)
{
console.log('starting ' + n);
return new Promise(resolve => {
setTimeout(() => {
console.log('finished ' + n);
resolve(n);
}, 1000 * (Math.random(5) + 1));
});
}
let promises = array.map(async (n) => {
return await getResult(n);
});
console.log('map finished');
Promise.all(promises).then(console.log);

你将看到.map调用在任何异步操作完成之前立即完成。

如果getResult总是返回一个承诺并且从不抛出错误,那么两者的行为将相同。

一些 promise 返回函数可能会在返回 promise 之前抛出错误,在这种情况下,将getResult调用包装在异步函数中会将抛出的错误转换为被拒绝的承诺,这可能很有用。

正如许多评论中所述,你永远不需要return await——这相当于在承诺链的末端添加.then(result=>result)——它(大部分)是无害的,但很有用。 只需使用return.

最新更新