Используйте async await с 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: Тип "Обещание<номер>[]" нельзя назначить типу "номер []". Тип "Обещание<номер> нельзя назначить типу" номер ".
Как я могу это исправить? Как я могу сделать async await
а также Array.map
работать вместе?
11 ответов
Проблема в том, что вы пытаетесь await
массив обещаний, а не обещание. Это не делает то, что вы ожидаете.
Когда объект перешел к await
не обещание, await
просто немедленно возвращает значение как есть, вместо того, чтобы пытаться разрешить его. Так как вы прошли await
массив (объектов Promise) здесь вместо Promise, значение, возвращаемое await, является просто тем массивом, который имеет тип Promise<number>[]
,
Что вам нужно сделать здесь, это позвонить Promise.all
на массиве, возвращаемом map
чтобы преобразовать его в одно обещание перед await
это
Согласно документам MDN дляPromise.all
:
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;
}));
Это устранит конкретную ошибку, с которой вы столкнулись здесь.
Это самый простой способ сделать это.
await Promise.all(
arr.map(async (element) => {
....
})
)
Нижеприведенное решение для асинхронной обработки всех элементов массива И сохранения порядка:
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.all ожидает разрешения всех из них и возвращает массив с разрешенными значениями по порядку.
Есть и другое решение, если вы используете не собственные обещания, а 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.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)
}
Решение с использованием карты modern-async():
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 - имеет
Это может помочь кому-то.
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))