Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает Promise.all
Определение и назначение
Promise.all() — это статический метод, который принимает массив Promise и возвращает новый Promise, который разрешается когда ВСЕ Promise в массиве разрешены, или отклоняется если хотя бы ОДИН Promise отклонен.
Базовая структура
Promise.all([promise1, promise2, promise3])
.then(results => {
// results = [result1, result2, result3]
// Вызвётся только если все promise разрешены
})
.catch(error => {
// Вызовется если хотя бы один promise отклонен
});
Ключевые особенности
1. Параллельное выполнение
Основное отличие от последовательного выполнения — все Promise выполняются ОДНОВРЕМЕННО, а не один за другим:
// Неправильно — последовательно (времени: 1 + 2 + 3 = 6 секунд)
const result1 = await fetchUser(1); // 1 сек
const result2 = await fetchUser(2); // 2 сек
const result3 = await fetchUser(3); // 3 сек
// Правильно — параллельно (времени: max(1, 2, 3) = 3 секунды)
const [result1, result2, result3] = await Promise.all([
fetchUser(1),
fetchUser(2),
fetchUser(3)
]);
Время выполнения = времени самого долгого Promise, а не сумме всех!
2. Сохранение порядка результатов
Результаты возвращаются в том же порядке, что и Promise в исходном массиве, даже если они разрешаются в другом порядке:
const promises = [
new Promise(resolve => setTimeout(() => resolve('A'), 300)),
new Promise(resolve => setTimeout(() => resolve('B'), 100)),
new Promise(resolve => setTimeout(() => resolve('C'), 200))
];
Promise.all(promises).then(results => {
console.log(results); // ['A', 'B', 'C'] — порядок сохранён!
});
// Порядок разрешения: B (100ms) -> C (200ms) -> A (300ms)
// Но результаты: ['A', 'B', 'C']
3. Fail-fast поведение
Если хотя бы один Promise отклонен, весь Promise.all немедленно отклоняется:
const promises = [
Promise.resolve('Success 1'),
Promise.reject('Error!'),
Promise.resolve('Success 2')
];
Promise.all(promises)
.then(results => {
console.log('Never called');
})
.catch(error => {
console.log(error); // 'Error!'
});
// Это произойдёт, даже если бы был setTimeout(3000) на третий Promise
Практические примеры
Пример 1: Загрузка нескольких ресурсов
function fetchUserData(userId) {
return Promise.all([
fetch(`/api/users/${userId}`).then(r => r.json()),
fetch(`/api/users/${userId}/posts`).then(r => r.json()),
fetch(`/api/users/${userId}/comments`).then(r => r.json())
]);
}
fetchUserData(1).then(([user, posts, comments]) => {
console.log('User:', user);
console.log('Posts:', posts);
console.log('Comments:', comments);
});
// Все три запроса идут параллельно
// Время: max(fetch времени), а не сумма
Пример 2: React компонент с несколькими API запросами
function useUserData(userId) {
const [data, setData] = React.useState(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);
React.useEffect(() => {
Promise.all([
fetch(`/api/users/${userId}`).then(r => r.json()),
fetch(`/api/users/${userId}/posts`).then(r => r.json())
])
.then(([user, posts]) => {
setData({ user, posts });
})
.catch(err => {
setError(err);
})
.finally(() => {
setLoading(false);
});
}, [userId]);
return { data, loading, error };
}
Пример 3: Обработка смешанных типов (Promise и значения)
Promise.all работает не только с Promise, но и с обычными значениями:
const mixed = Promise.all([
Promise.resolve(1),
2, // обычное значение
Promise.resolve(3),
'four' // обычное значение
]);
mixed.then(results => {
console.log(results); // [1, 2, 3, 'four']
});
Альтернативные методы
Promise.allSettled() — хочешь все результаты, даже ошибки
const promises = [
Promise.resolve('Success'),
Promise.reject('Error'),
Promise.resolve('Another Success')
];
Promise.allSettled(promises).then(results => {
console.log(results);
// [
// { status: 'fulfilled', value: 'Success' },
// { status: 'rejected', reason: 'Error' },
// { status: 'fulfilled', value: 'Another Success' }
// ]
});
// Ключевое отличие: ВСЕ результаты возвращаются, в том числе ошибки!
Promise.race() — первый результат выигрывает
const promise1 = new Promise(resolve => setTimeout(() => resolve('First'), 100));
const promise2 = new Promise(resolve => setTimeout(() => resolve('Second'), 500));
Promise.race([promise1, promise2]).then(result => {
console.log(result); // 'First' — быстрее
});
Promise.any() — первый успешный результат
const promise1 = Promise.reject('Error 1');
const promise2 = Promise.resolve('Success');
const promise3 = Promise.reject('Error 3');
Promise.any([promise1, promise2, promise3]).then(result => {
console.log(result); // 'Success' — первый успешный
});
Обработка ошибок
Проблема: какой Promise ошибился?
Promise.all([
fetchUser(1),
fetchUser(2),
fetchUser(3)
]).catch(error => {
console.log(error); // Какой из трёх ошибился? Не знаем!
});
Решение: Используй Promise.allSettled для диагностики:
Promise.allSettled([
fetchUser(1),
fetchUser(2),
fetchUser(3)
]).then(results => {
results.forEach((result, index) => {
if (result.status === 'rejected') {
console.log(`User ${index + 1} fetch failed:`, result.reason);
}
});
});
Обработка ошибок с retry логикой
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
} catch (error) {
if (i === retries - 1) throw error; // последняя попытка
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
Promise.all([
fetchWithRetry('/api/users/1'),
fetchWithRetry('/api/users/2'),
fetchWithRetry('/api/users/3')
]).catch(error => {
console.log('Ошибка даже после повторов:', error);
});
Сложные сценарии
Вложенные Promise.all
// Загрузить пользователей, потом для каждого загрузить посты
const userIds = [1, 2, 3];
Promise.all(userIds.map(id => fetch(`/api/users/${id}`)))
.then(responses => Promise.all(responses.map(r => r.json())))
.then(users => {
return Promise.all(
users.map(user =>
Promise.all([
Promise.resolve(user),
fetch(`/api/posts?userId=${user.id}`).then(r => r.json())
])
)
);
})
.then(usersWithPosts => {
console.log(usersWithPosts);
});
// Или проще через async/await:
async function loadUsersWithPosts() {
const users = await fetchUsers();
const userPosts = await Promise.all(
users.map(user => fetchUserPosts(user.id))
);
return users.map((user, i) => ({
...user,
posts: userPosts[i]
}));
}
Лучшие практики
- Используй Promise.all для параллельных операций — это даёт 3x ускорение для 3 операций
- Помни про fail-fast — один error = всё падает
- Используй allSettled для диагностики — когда нужно узнать, какой Promise ошибился
- async/await часто красивее — но понимай, что под капотом это Promise.all
- Порядок результатов сохранён — не нужно переставлять результаты
- Не забывай об error handling — даже если думаешь, что ошибки не будет
Эквивалент в async/await
// Promise.all
Promise.all([p1, p2, p3]).then(results => { ... });
// Эквивалент в async/await
const results = await Promise.all([p1, p2, p3]);
// ... код
// Это ПАРАЛЛЕЛЬНО!
const r1 = await p1; // последовательно (медленно)
const r2 = await p2;
const r3 = await p3;
Основной урок: Promise.all — это инструмент для параллелизма. Используй его для ускорения, когда операции независимы.