对Array.map使用异步等待


170

给出以下代码:

var arr = [1,2,3,4,5];

var results: number[] = await arr.map(async (item): Promise<number> => {
        await callAsynchronousOperation(item);
        return item + 1;
    });

产生以下错误:

TS2322:类型'Promise <number> []'无法分配给类型'number []'。类型“ Promise <数字>”不可分配给类型“数字”。

我该如何解决?怎样才能让async awaitArray.map一起工作?


6
为什么要尝试将同步操作转换为异步操作? arr.map()是同步的,不会返回承诺。
jfriend00

2
您无法将异步操作发送给函数,例如map,该函数需要同步的函数并希望其起作用。
异端猴子

1
@ jfriend00内部函数中有许多await语句。它实际上是一个很长的函数,我只是对其进行了简化以使其易于阅读。我现在添加了一个await调用,以使其更清楚为什么它应该是异步的。
阿隆

您需要等待返回承诺的内容,而不是返回数组的内容。
jfriend00

2
要意识到的一件有用的事情是,每当您将一个函数标记为时async,您就使该函数返回一个承诺。因此,当然,异步映射会返回一系列承诺:)
安东尼·曼宁·富兰克林

Answers:


380

这里的问题是,您尝试await的是承诺而不是承诺。这没有达到您的期望。

当传递给的对象await不是Promise时,await只需立即按原样返回值,而不是尝试解析它。因此,由于您await在此处传递了一个Promise对象的数组而不是Promise,因此await返回的值就是该数组,其类型为Promise<number>[]

您需要在此处执行的操作是调用Promise.all返回的数组map,以便在使用之前将其转换为单个Promise await

根据MDN文档Promise.all

Promise.all(iterable)方法返回一个promise,当可迭代参数中的所有promise都已解决时,该promise会解决;或者以第一个传递的promise拒绝为由拒绝。

因此,在您的情况下:

var arr = [1, 2, 3, 4, 5];

var results: number[] = await Promise.all(arr.map(async (item): Promise<number> => {
    await callAsynchronousOperation(item);
    return item + 1;
}));

这将解决您在此处遇到的特定错误。


1
什么做的:冒号是什么意思?
丹尼尔说莫妮卡(

11
@DanielPendergast用于TypeScript中的类型注释。
Ajedi32

在异步映射函数中callAsynchronousOperation(item);使用和不使用调用之间有什么区别await
nerdizzle

@nerdizzle听起来像是另一个问题的理想人选。不过,基本上,await该函数将在继续之前等待异步操作完成(或失败),否则它将立即继续而不等待。
Ajedi32

@ Ajedi32 thx的回应。但是如果没有在异步映射中等待,就不可能再等待函数的返回结果了吗?
nerdizzle

15

如果您不使用本地Promises,而是使用Bluebird,则还有另一种解决方案。

您也可以尝试使用Promise.map(),将array.map和Promise.all混合使用

在您的情况下:

  var arr = [1,2,3,4,5];

  var results: number[] = await Promise.map(arr, async (item): Promise<number> => {
    await callAsynchronousOperation(item);
    return item + 1;
  });

2
这是不同的-它不会并行运行所有操作,而是按顺序执行它们。
Andrey Tserkus

5
@AndreyTserkus Promise.mapSeriesPromise.each先后顺序,Promise.map立即全部启动。
Kiechlus,2017年

1
@AndreyTserkus,您可以通过提供concurrency选项并行运行所有或某些操作。

11
值得一提的是,它不是普通的JS。
Michal '18

@Michal是的,这是SyntaxError
CS QGB


2

我建议如上所述使用Promise.all,但是如果您真的想避免这种方法,则可以执行for或任何其他循环:

const arr = [1,2,3,4,5];
let resultingArr = [];
for (let i in arr){
  await callAsynchronousOperation(i);
  resultingArr.push(i + 1)
}

6
Promise.all对于数组的每个元素都是异步的。这将是一个同步,它必须等待完成一个元素才能启动下一个元素。
圣地亚哥·门多萨·拉米雷斯

对于尝试这种方法的人,请注意for..of是迭代数组内容的正确方法,而for..in是遍历索引的方法。
ralfoide

2

以下解决方案可异步处理数组的所有元素并保留顺序:

const arr = [1, 2, 3, 4, 5, 6, 7, 8];
const randomDelay = () => new Promise(resolve => setTimeout(resolve, Math.random() * 1000));

const calc = async n => {
  await randomDelay();
  return n * 2;
};

const asyncFunc = async () => {
  const unresolvedPromises = arr.map(n => calc(n));
  const results = await Promise.all(unresolvedPromises);
};

asyncFunc();

codepen

注意,我们仅“等待” Promise.all。我们多次调用calc而不使用“ await”,并且立即收集了一系列未解决的Promise。然后Promise.all等待所有它们的解析,并按顺序返回具有已解析值的数组。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.