将 async wait 与 Array.map 结合使用

问题描述 投票:0回答:11

给出以下代码:

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[]”。 类型“Promise”不可分配给类型“number”。

我该如何修复它?如何让

async await
Array.map
一起工作?

javascript typescript promise async-await ecmascript-2017
11个回答
1180
投票

这里的问题是你试图

await
一组承诺而不是一个承诺。这不会达到您的预期。

当传递给

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

您可能想要做的是在

Promise.all
返回的数组上调用
map
,以便在
await
之前将其转换为单个 Promise。

根据 MDN 文档

Promise.all

Promise.all(iterable)
方法返回一个解决的承诺 当可迭代参数中的所有承诺都已解决时,或者 拒绝的原因是第一个通过的 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.any
Promise.race
代替
Promise.all
,尽管在大多数情况下(几乎肯定包括这个)
Promise.all
将会是你想要的。


102
投票

这是最简单的方法。

await Promise.all(
    arr.map(async (element) => {
        ....
    })
)

69
投票

下面的解决方案可以正确使用 async wait 和 Array.map 。并行、异步处理数组的所有元素并保留顺序:

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(calc);
  const results = await Promise.all(unresolvedPromises);
  document.write(results);
};

document.write('calculating...');
asyncFunc();

还有codepen

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


23
投票

如果您不使用原生 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;
  });

16
投票

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


16
投票

您可以使用:

for await (let resolvedPromise of arrayOfPromises) {
  console.log(resolvedPromise)
}

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of

如果您想使用

Promise.all()
代替,您可以选择
Promise.allSettled()
这样您就可以更好地控制被拒绝的承诺。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled


5
投票

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

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

FYI:如果您想迭代数组的项目,而不是索引(@ralfoide 的评论),请在

of
语句中使用
in
而不是
let i in arr


4
投票

使用 modern-async 的 map() 的解决方案:

import { map } from 'modern-async'

...
const result = await map(myArray, async (v) => {
    ...
})

使用该库的优点是您可以使用 mapLimit()mapSeries() 控制并发。


1
投票

顺便说一句,请注意,您可以轻松创建一个扩展以使其更简洁,并像这样使用它:

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

var results = await arr.mapAsync(async (e)  => {
    return await callAsynchronousOperation(e);
});

您只需要创建

extensions.ts
文件:

export {};

declare global {
  interface Array<T> {
    mapAsync<U>(mapFn: (value: T, index: number, array: T[]) => Promise<U>): Promise<U[]>;
}

if (!Array.prototype.mapAsync) {
  Array.prototype.mapAsync = async function <U, T>(
    mapFn: (value: T, index: number, array: T[]) => Promise<U>
  ): Promise<U[]> {
    return await Promise.all(
      (this as T[]).map(async (v, i, a): Promise<U> => {
        return await mapFn(v, i, a);
      })
    );
  };
}

0
投票

我在 BE 端有一个任务,从存储库中查找所有实体,添加新的属性 url 并返回到控制器层。这就是我实现它的方法(感谢 Ajedi32 的回复):

async findAll(): Promise<ImageResponse[]> {
    const images = await this.imageRepository.find(); // This is an array of type Image (DB entity)
    const host = this.request.get('host');
    const mappedImages = await Promise.all(images.map(image => ({...image, url: `http://${host}/images/${image.id}`}))); // This is an array of type Object
    return plainToClass(ImageResponse, mappedImages); // Result is an array of type ImageResponse
  }

注意:图像(实体)没有属性 url,但 ImageResponse - 有


0
投票

有一种使用 Array.fromAsync 来执行此操作的新方法:

await Array.fromAsync([1,2,3,4,5].map(async (i) => await callAsynchronousOperation(i) )

我想在 Node 中使用它,但仅在 22 中可用。

© www.soinside.com 2019 - 2024. All rights reserved.