将异步等待与 Array.map 一起使用

IT技术 javascript typescript promise async-await ecmascript-2017
2021-01-21 14:58:13

鉴于以下代码:

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<number> 不可分配给类型 'number'。

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

6个回答

这里的问题是您正在尝试await一系列Promise而不是Promise。这不符合您的预期。

当传递给的对象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;
}));

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

根据您正在尝试做什么,您也可以考虑使用Promise.allSettled,Promise.anyPromise.race代替Promise.all,尽管在大多数情况下(几乎肯定包括这种情况)Promise.all将是您想要的。

什么做的:冒号是什么意思?
2021-03-22 14:58:13
@nerdizzle 这听起来像是另一个问题的好候选人。基本上,await该函数将在继续之前等待异步操作完成(或失败),否则它将立即继续而不等待。
2021-03-24 14:58:13
@DanielPendergast 它用于 TypeScript 中的类型注释。
2021-03-27 14:58:13
@Ajedi32 感谢回复。但是如果没有异步映射中的等待,就不可能再等待函数的重新结果?
2021-04-02 14:58:13
在异步映射函数内部调用callAsynchronousOperation(item);和不调用什么区别await
2021-04-09 14:58:13

如果您使用的不是原生 Promise 而是 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;
  });
@AndreyTserkus 您可以通过提供concurrency选项并行运行所有或部分操作
2021-03-13 14:58:13
它是不同的 - 它不会并行运行所有操作,而是按顺序执行它们。
2021-03-18 14:58:13
值得一提的是,它不是普通的 JS。
2021-04-05 14:58:13
@AndreyTserkusPromise.mapSeriesPromise.each顺序,立即Promise.map启动它们。
2021-04-09 14:58:13

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

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();

还有代码笔

注意我们只“等待”Promise.all。我们在没有“await”的情况下多次调用 calc,我们立即收集了一系列未解决的Promise。然后 Promise.all 等待所有的解析并按顺序返回一个包含解析值的数组。

在使用 Javascript 的这些年里,由于 js 的异步特性,我一直在映射数组而不打乱顺序时遇到问题。我已经使用了 promise.all,但从未想过它会按原样解决它们。这个答案从字面上改变了我现在写地图和其他东西的方式。上帝保佑。
2021-04-02 14:58:13

如果映射到 Promise 数组,则可以将它们全部解析为数字数组。参见 Promise.all

我建议使用上面提到的 Promise.all ,但如果你真的想避免这种方法,你可以做一个 for 或任何其他循环:

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

仅供参考:如果您想迭代数组的项目,而不是索引(@ralfoide 的评论),请使用of而不是ininsidelet i in arr语句。

对于尝试这种方法的人,请注意 for..of 是迭代数组内容的正确方法,而 for..in 迭代索引。
2021-03-26 14:58:13
Promise.all 对数组的每个元素都是异步的。这将是一个同步,它必须等待完成一个元素才能开始下一个元素。
2021-04-09 14:58:13