Как правильно использовать async/await внутри Array.map в TypeScript?
Проблема: map возвращает массив Promise, а не данных
Вы столкнулись с классической ловушкой при использовании async/await внутри Array.map. Дело в том, что map — это синхронный метод. Когда вы передаёте ему асинхронную функцию (с пометкой async), она немедленно возвращает Promise, а не дожидается его разрешения. Поэтому map собирает массив из этих Promise, а не из реальных значений.
Решение: комбинируем map с Promise.all
Правильный подход — использовать Promise.all, который принимает массив Promise и возвращает единый Promise, который разрешается, когда все переданные Promise завершены. Вот как это выглядит:
const userIds: number[] = [1, 2, 3, 4, 5];
async function fetchUserData(id: number): Promise<{ name: string; age: number }> {
const response = await fetch(`https://api.example.com/users/${id}`);
return response.json();
}
// Шаг 1: создаём массив Promise через map
const userPromises = userIds.map((id) => fetchUserData(id));
// Шаг 2: дожидаемся всех Promise с помощью Promise.all
const users = await Promise.all(userPromises);
console.log(users); // Теперь это массив объектов с данными пользователей
Полный пример с обработкой ошибок
В реальном проекте важно обрабатывать ошибки. Используйте try/catch:
async function getAllUsers(userIds: number[]): Promise<{ name: string; age: number }[]> {
try {
const userPromises = userIds.map((id) => fetchUserData(id));
const users = await Promise.all(userPromises);
return users;
} catch (error) {
console.error('Ошибка при загрузке пользователей:', error);
throw error; // пробрасываем ошибку дальше
}
}
// Использование
(async () => {
const userIds = [1, 2, 3, 4, 5];
const users = await getAllUsers(userIds);
console.log(users);
})();
Важные нюансы
- Promise.all — fail-fast: Если хотя бы один Promise в массиве завершится с ошибкой, весь
Promise.allсразу же упадёт с этой ошибкой. Если вам нужно обработать каждый запрос независимо, используйтеPromise.allSettled(ES2020). - Типизация: TypeScript автоматически выведет тип результата
Promise.allкакPromise<{ name: string; age: number }[]>, так что дополнительно указывать типы не нужно. - Производительность: Запросы выполняются параллельно, что быстрее последовательного выполнения. Если вам нужно ограничить количество одновременных запросов (например, чтобы не перегрузить API), используйте библиотеки вроде
p-limit. - Async в map — это нормально: Сам по себе
asyncвнутриmapне является ошибкой. Ошибка — это не дожидаться результата. Комбинацияmap+Promise.all— стандартный паттерн.
Альтернатива: for...of с await (последовательное выполнение)
Если вам нужно выполнять запросы строго по очереди (например, для сохранения порядка или из-за ограничений API), используйте цикл for...of:
async function getUsersSequentially(userIds: number[]) {
const users: { name: string; age: number }[] = [];
for (const id of userIds) {
const user = await fetchUserData(id);
users.push(user);
}
return users;
}
Этот подход медленнее, но даёт полный контроль над последовательностью и позволяет обрабатывать ошибки индивидуально.
Вывод
Запомните правило: если вы используете async/await внутри map, результат всегда будет массивом Promise. Чтобы получить массив данных, оберните вызов map в Promise.all и дождитесь его с помощью await.
Дополнительный совет: используйте Promise.allSettled для устойчивости к ошибкам
Если вам нужно, чтобы один неудачный запрос не "убивал" весь массив результатов, используйте Promise.allSettled вместо Promise.all. Этот метод возвращает массив объектов с информацией о статусе каждого Promise: fulfilled (успех) или rejected (ошибка).
async function getUsersSafe(userIds: number[]) {
const results = await Promise.allSettled(
userIds.map((id) => fetchUserData(id))
);
// Фильтруем только успешные результаты
const users = results
.filter((result): result is PromiseFulfilledResult<{ name: string; age: number }> =>
result.status === 'fulfilled'
)
.map((result) => result.value);
// Логируем ошибки
results
.filter((result) => result.status === 'rejected')
.forEach((result) => console.error('Ошибка загрузки:', result.reason));
return users;
}
Этот подход особенно полезен, когда вы загружаете данные из ненадёжных источников или когда частичный результат лучше, чем полное отсутствие данных. TypeScript поддерживает Promise.allSettled начиная с версии ES2020 — убедитесь, что ваш tsconfig.json настроен соответствующим образом ("target": "ES2020" или выше).
Ответов пока нет.
Войдите чтобы оставить ответ