给出以下代码:
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
一起工作?
这里的问题是你试图
await
一组承诺而不是一个承诺。这不会达到您的预期。
当传递给
await
的对象不是 Promise 时,await
只是立即按原样返回值,而不是尝试解析它。因此,由于您在这里传递了 await
一个(Promise 对象的)数组而不是 Promise,await 返回的值就是该数组,其类型为 Promise<number>[]
。
您可能想要做的是在
Promise.all
返回的数组上调用 map
,以便在 await
之前将其转换为单个 Promise。
根据 MDN 文档
Promise.all
:
方法返回一个解决的承诺 当可迭代参数中的所有承诺都已解决时,或者 拒绝的原因是第一个通过的 Promise 被拒绝。Promise.all(iterable)
所以在你的情况下:
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
将会是你想要的。
这是最简单的方法。
await Promise.all(
arr.map(async (element) => {
....
})
)
下面的解决方案可以正确使用 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 等待所有这些值的解析并按顺序返回包含解析值的数组。
如果您不使用原生 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;
});
如果映射到 Promise 数组,则可以将它们全部解析为数字数组。 请参阅 Promise.all。
您可以使用:
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
我建议使用上面提到的 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
。
使用 modern-async 的 map() 的解决方案:
import { map } from 'modern-async'
...
const result = await map(myArray, async (v) => {
...
})
使用该库的优点是您可以使用 mapLimit() 或 mapSeries() 控制并发。
顺便说一句,请注意,您可以轻松创建一个扩展以使其更简洁,并像这样使用它:
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);
})
);
};
}
我在 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 - 有
有一种使用 Array.fromAsync 来执行此操作的新方法:
await Array.fromAsync([1,2,3,4,5].map(async (i) => await callAsynchronousOperation(i) )
我想在 Node 中使用它,但仅在 22 中可用。