使用 Array.map 的异步等待
Use async await with Array.map
给定以下代码:
var arr = [1,2,3,4,5];
var results: number[] = await arr.map(async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
});
产生以下错误:
TS2322: Type 'Promise<number>[]' is not assignable to type 'number[]'.
Type 'Promise<number> is not assignable to type 'number'.
我该如何解决?如何让 async await
和 Array.map
一起工作?
如果映射到 Promise 数组,则可以将它们全部解析为数字数组。参见 Promise.all。
这里的问题是您正在尝试 await
一组承诺而不是一个承诺。这不符合您的预期。
当传递给 await
的对象不是 Promise 时,await
只是立即 returns 原样的值,而不是尝试解析它。因此,由于您在此处传递了 await
一个(Promise 对象的)数组而不是 Promise,因此 await 返回的值只是该数组,其类型为 Promise<number>[]
.
您可能想要做的是在 map
返回的数组上调用 Promise.all
,以便在 await
ing 之前将其转换为单个 Promise。
The Promise.all(iterable)
method returns a promise that resolves
when all of the promises in the iterable argument have resolved, or
rejects with the reason of the first passed promise that rejects.
所以在你的情况下:
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
, or Promise.race
而不是 Promise.all
,但在大多数情况下(几乎肯定包括这个)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
而不是 in
在 let i in arr
语句中。
如果您不使用本机 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;
});
下面的解决方案可以正确地同时使用 async await 和 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(n => calc(n));
const results = await Promise.all(unresolvedPromises);
};
asyncFunc();
还有codepen.
注意我们只“等待”Promise.all。我们在没有“等待”的情况下多次调用 calc,我们立即收集了一系列未解决的承诺。然后 Promise.all 等待所有这些的解析和 returns 一个数组,其中包含按顺序解析的值。
我在 BE 方面有一项任务是从 repo 中找到所有实体,并添加一个新的 属性 url 和 return 到控制器层。我是这样实现的(感谢 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 - 有
您可以使用:
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
使用modern-async's map()的解决方案:
import { map } from 'modern-async'
...
const result = await map(myArray, async (v) => {
...
})
使用该库的优点是您可以使用 mapLimit() or mapSeries().
控制并发性
这可能会对某人有所帮助。
const APISimulator = (v) => new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ data: v });
}, v * 100);
});
const arr = [7, 6, 5, 1, 2, 3];
const res = () => arr.reduce(async (memo, v, i) => {
const results = await memo;
console.log(`proccessing item-${i + 1} :`, v)
await APISimulator(v);
console.log(`completed proccessing-${i + 1} :`, v)
return [...results, v];
}, []);
res().then(proccessed => console.log(proccessed))
这是最简单的方法。
await Promise.all(
arr.map(async (element) => {
....
})
)
给定以下代码:
var arr = [1,2,3,4,5];
var results: number[] = await arr.map(async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
});
产生以下错误:
TS2322: Type 'Promise<number>[]' is not assignable to type 'number[]'. Type 'Promise<number> is not assignable to type 'number'.
我该如何解决?如何让 async await
和 Array.map
一起工作?
如果映射到 Promise 数组,则可以将它们全部解析为数字数组。参见 Promise.all。
这里的问题是您正在尝试 await
一组承诺而不是一个承诺。这不符合您的预期。
当传递给 await
的对象不是 Promise 时,await
只是立即 returns 原样的值,而不是尝试解析它。因此,由于您在此处传递了 await
一个(Promise 对象的)数组而不是 Promise,因此 await 返回的值只是该数组,其类型为 Promise<number>[]
.
您可能想要做的是在 map
返回的数组上调用 Promise.all
,以便在 await
ing 之前将其转换为单个 Promise。
The
Promise.all(iterable)
method returns a promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first passed promise that rejects.
所以在你的情况下:
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
, or Promise.race
而不是 Promise.all
,但在大多数情况下(几乎肯定包括这个)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
而不是 in
在 let i in arr
语句中。
如果您不使用本机 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;
});
下面的解决方案可以正确地同时使用 async await 和 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(n => calc(n));
const results = await Promise.all(unresolvedPromises);
};
asyncFunc();
还有codepen.
注意我们只“等待”Promise.all。我们在没有“等待”的情况下多次调用 calc,我们立即收集了一系列未解决的承诺。然后 Promise.all 等待所有这些的解析和 returns 一个数组,其中包含按顺序解析的值。
我在 BE 方面有一项任务是从 repo 中找到所有实体,并添加一个新的 属性 url 和 return 到控制器层。我是这样实现的(感谢 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 - 有
您可以使用:
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
使用modern-async's map()的解决方案:
import { map } from 'modern-async'
...
const result = await map(myArray, async (v) => {
...
})
使用该库的优点是您可以使用 mapLimit() or mapSeries().
控制并发性这可能会对某人有所帮助。
const APISimulator = (v) => new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ data: v });
}, v * 100);
});
const arr = [7, 6, 5, 1, 2, 3];
const res = () => arr.reduce(async (memo, v, i) => {
const results = await memo;
console.log(`proccessing item-${i + 1} :`, v)
await APISimulator(v);
console.log(`completed proccessing-${i + 1} :`, v)
return [...results, v];
}, []);
res().then(proccessed => console.log(proccessed))
这是最简单的方法。
await Promise.all(
arr.map(async (element) => {
....
})
)