Ты открываешь страницу профиля на GitHub. Браузер должен загрузить: данные пользователя, его репозитории, подписчиков, статистику контрибуций. Это четыре независимых API-запроса.
Наивный подход — ждать каждый по очереди:
const user = await fetchUser(id) // 200мс
const repos = await fetchRepos(id) // 300мс
const followers = await fetchFollowers(id) // 150мс
const stats = await fetchStats(id) // 250мс
// Итого: 200 + 300 + 150 + 250 = 900мс — пользователь ждёт почти секунду!Правильный подход — параллельно:
const [user, repos, followers, stats] = await Promise.all([
fetchUser(id), fetchRepos(id), fetchFollowers(id), fetchStats(id)
])
// Итого: max(200, 300, 150, 250) = 300мс — в 3 раза быстрее!const [a, b, c] = await Promise.all([...])Запускает промисы параллельно. Разрешается когда все выполнены. Если хотя бы один отклонён — весь Promise.all отклоняется немедленно:
const [user, repos, followers] = await Promise.all([
fetchUser(id), // 200мс
fetchRepos(id), // 300мс
fetchFollowers(id), // 150мс
])
// Результат через max(200, 300, 150) = 300мсИспользуй когда все данные обязательны для отрисовки страницы.
В отличие от all, не прерывается при ошибке. Ждёт все промисы и возвращает массив результатов:
const results = await Promise.allSettled([
fetchNews(), // может упасть
fetchWeather(), // может упасть
fetchStocks(), // может упасть
])
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log('Данные:', result.value)
} else {
console.warn('Виджет недоступен:', result.reason.message)
}
})
// Один упавший виджет не ломает всю страницуИспользуй для виджетов, аналитики, необязательных данных.
Возвращает результат первого завершившегося промиса (fulfilled или rejected):
// Timeout-паттерн — запрос не должен висеть вечно
async function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout ${ms}мс`)), ms)
)
return Promise.race([promise, timeout])
}
const data = await withTimeout(fetchHeavyReport(), 5000)Как race, но игнорирует отклонения. Разрешается при первом fulfilled:
// Несколько CDN — используем самый быстрый доступный
const asset = await Promise.any([
fetch('https://cdn1.example.com/bundle.js'),
fetch('https://cdn2.example.com/bundle.js'),
fetch('https://cdn3.example.com/bundle.js'),
])
// Если все три упали — AggregateError| Метод | Поведение при ошибке | Когда использовать |
|---|---|---|
| Promise.all | Отменяет всё | Все данные обязательны |
| Promise.allSettled | Продолжает | Ошибки в части допустимы |
| Promise.race | Первый wins | Таймаут, самый быстрый |
| Promise.any | Игнорирует ошибки | Первый успешный |
1. Последовательные await для независимых запросов:
// Медленно — ждём каждый по очереди:
const a = await fetch('/api/a')
const b = await fetch('/api/b') // ждёт пока не завершится /api/a
// Быстро — параллельно:
const [a, b] = await Promise.all([fetch('/api/a'), fetch('/api/b')])2. Не обработали отклонение в Promise.all:
// Сломано — если один упадёт, вся страница сломается:
const [user, orders] = await Promise.all([fetchUser(id), fetchOrders(id)])
// Исправлено — оборачиваем в try/catch:
try {
const [user, orders] = await Promise.all([fetchUser(id), fetchOrders(id)])
} catch (e) {
console.error('Не удалось загрузить:', e.message)
}3. Передают промисы вместо функций:
// Внимание — промисы запускаются сразу при создании, не при передаче в all:
const p1 = fetchUser(id) // запрос уже отправлен!
const p2 = fetchOrders(id) // этот тоже!
await Promise.all([p1, p2]) // ждём оба — это нормальноuseQueriesЗагрузка дашборда: Promise.all vs sequential, и Promise.allSettled для виджетов
const delay = ms => new Promise(r => setTimeout(r, ms))
// Симуляция API-эндпоинтов
async function fetchUser(id) {
await delay(100)
return { id, name: 'Алексей Иванов', role: 'admin', plan: 'pro' }
}
async function fetchStats(id) {
await delay(200)
return { orders: 47, revenue: 284500, returns: 3 }
}
async function fetchRecentOrders(id) {
await delay(150)
return [
{ id: 1001, product: 'MacBook Pro', status: 'delivered', amount: 189990 },
{ id: 1002, product: 'AirPods', status: 'shipped', amount: 24990 },
]
}
// Необязательные виджеты — могут упасть
async function fetchNewsWidget() {
await delay(80)
return [{ title: 'Новые поступления iPhone 15' }]
}
async function fetchWeatherWidget() {
await delay(60)
throw new Error('Weather API недоступен') // симулируем ошибку
}
async function fetchBannerWidget() {
await delay(120)
return { text: 'Скидки до 30% на электронику!' }
}
// Загрузка основных данных — все обязательны
async function loadDashboard(userId) {
console.log('Загружаем дашборд...')
const start = Date.now()
const [user, stats, orders] = await Promise.all([
fetchUser(userId),
fetchStats(userId),
fetchRecentOrders(userId),
])
console.log(`Основные данные: ${Date.now() - start}мс (параллельно)`)
console.log(`Пользователь: ${user.name} [${user.plan}]`)
console.log(`Выручка: ${stats.revenue.toLocaleString('ru-RU')} ₽`)
console.log(`Заказов: ${orders.length}`)
// Необязательные виджеты — ошибки не ломают страницу
const widgets = await Promise.allSettled([
fetchNewsWidget(),
fetchWeatherWidget(), // упадёт!
fetchBannerWidget(),
])
const widgetNames = ['Новости', 'Погода', 'Баннер']
widgets.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log(`Виджет "${widgetNames[i]}": загружен`)
} else {
console.warn(`Виджет "${widgetNames[i]}": недоступен (${result.reason.message})`)
}
})
}
loadDashboard(1)Ты открываешь страницу профиля на GitHub. Браузер должен загрузить: данные пользователя, его репозитории, подписчиков, статистику контрибуций. Это четыре независимых API-запроса.
Наивный подход — ждать каждый по очереди:
const user = await fetchUser(id) // 200мс
const repos = await fetchRepos(id) // 300мс
const followers = await fetchFollowers(id) // 150мс
const stats = await fetchStats(id) // 250мс
// Итого: 200 + 300 + 150 + 250 = 900мс — пользователь ждёт почти секунду!Правильный подход — параллельно:
const [user, repos, followers, stats] = await Promise.all([
fetchUser(id), fetchRepos(id), fetchFollowers(id), fetchStats(id)
])
// Итого: max(200, 300, 150, 250) = 300мс — в 3 раза быстрее!const [a, b, c] = await Promise.all([...])Запускает промисы параллельно. Разрешается когда все выполнены. Если хотя бы один отклонён — весь Promise.all отклоняется немедленно:
const [user, repos, followers] = await Promise.all([
fetchUser(id), // 200мс
fetchRepos(id), // 300мс
fetchFollowers(id), // 150мс
])
// Результат через max(200, 300, 150) = 300мсИспользуй когда все данные обязательны для отрисовки страницы.
В отличие от all, не прерывается при ошибке. Ждёт все промисы и возвращает массив результатов:
const results = await Promise.allSettled([
fetchNews(), // может упасть
fetchWeather(), // может упасть
fetchStocks(), // может упасть
])
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log('Данные:', result.value)
} else {
console.warn('Виджет недоступен:', result.reason.message)
}
})
// Один упавший виджет не ломает всю страницуИспользуй для виджетов, аналитики, необязательных данных.
Возвращает результат первого завершившегося промиса (fulfilled или rejected):
// Timeout-паттерн — запрос не должен висеть вечно
async function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout ${ms}мс`)), ms)
)
return Promise.race([promise, timeout])
}
const data = await withTimeout(fetchHeavyReport(), 5000)Как race, но игнорирует отклонения. Разрешается при первом fulfilled:
// Несколько CDN — используем самый быстрый доступный
const asset = await Promise.any([
fetch('https://cdn1.example.com/bundle.js'),
fetch('https://cdn2.example.com/bundle.js'),
fetch('https://cdn3.example.com/bundle.js'),
])
// Если все три упали — AggregateError| Метод | Поведение при ошибке | Когда использовать |
|---|---|---|
| Promise.all | Отменяет всё | Все данные обязательны |
| Promise.allSettled | Продолжает | Ошибки в части допустимы |
| Promise.race | Первый wins | Таймаут, самый быстрый |
| Promise.any | Игнорирует ошибки | Первый успешный |
1. Последовательные await для независимых запросов:
// Медленно — ждём каждый по очереди:
const a = await fetch('/api/a')
const b = await fetch('/api/b') // ждёт пока не завершится /api/a
// Быстро — параллельно:
const [a, b] = await Promise.all([fetch('/api/a'), fetch('/api/b')])2. Не обработали отклонение в Promise.all:
// Сломано — если один упадёт, вся страница сломается:
const [user, orders] = await Promise.all([fetchUser(id), fetchOrders(id)])
// Исправлено — оборачиваем в try/catch:
try {
const [user, orders] = await Promise.all([fetchUser(id), fetchOrders(id)])
} catch (e) {
console.error('Не удалось загрузить:', e.message)
}3. Передают промисы вместо функций:
// Внимание — промисы запускаются сразу при создании, не при передаче в all:
const p1 = fetchUser(id) // запрос уже отправлен!
const p2 = fetchOrders(id) // этот тоже!
await Promise.all([p1, p2]) // ждём оба — это нормальноuseQueriesЗагрузка дашборда: Promise.all vs sequential, и Promise.allSettled для виджетов
const delay = ms => new Promise(r => setTimeout(r, ms))
// Симуляция API-эндпоинтов
async function fetchUser(id) {
await delay(100)
return { id, name: 'Алексей Иванов', role: 'admin', plan: 'pro' }
}
async function fetchStats(id) {
await delay(200)
return { orders: 47, revenue: 284500, returns: 3 }
}
async function fetchRecentOrders(id) {
await delay(150)
return [
{ id: 1001, product: 'MacBook Pro', status: 'delivered', amount: 189990 },
{ id: 1002, product: 'AirPods', status: 'shipped', amount: 24990 },
]
}
// Необязательные виджеты — могут упасть
async function fetchNewsWidget() {
await delay(80)
return [{ title: 'Новые поступления iPhone 15' }]
}
async function fetchWeatherWidget() {
await delay(60)
throw new Error('Weather API недоступен') // симулируем ошибку
}
async function fetchBannerWidget() {
await delay(120)
return { text: 'Скидки до 30% на электронику!' }
}
// Загрузка основных данных — все обязательны
async function loadDashboard(userId) {
console.log('Загружаем дашборд...')
const start = Date.now()
const [user, stats, orders] = await Promise.all([
fetchUser(userId),
fetchStats(userId),
fetchRecentOrders(userId),
])
console.log(`Основные данные: ${Date.now() - start}мс (параллельно)`)
console.log(`Пользователь: ${user.name} [${user.plan}]`)
console.log(`Выручка: ${stats.revenue.toLocaleString('ru-RU')} ₽`)
console.log(`Заказов: ${orders.length}`)
// Необязательные виджеты — ошибки не ломают страницу
const widgets = await Promise.allSettled([
fetchNewsWidget(),
fetchWeatherWidget(), // упадёт!
fetchBannerWidget(),
])
const widgetNames = ['Новости', 'Погода', 'Баннер']
widgets.forEach((result, i) => {
if (result.status === 'fulfilled') {
console.log(`Виджет "${widgetNames[i]}": загружен`)
} else {
console.warn(`Виджет "${widgetNames[i]}": недоступен (${result.reason.message})`)
}
})
}
loadDashboard(1)Ты разрабатываешь страницу проекта в системе управления задачами (Jira-подобной). При открытии проекта нужно параллельно загрузить: - Детали проекта - Список задач - Список участников А также опционально (через `allSettled`) загрузить виджеты активности и комментарии, которые могут быть недоступны. Покажи разницу во времени: последовательно vs параллельно.
Promise.all([fetchProject(id), fetchTasks(id), fetchMembers(id)]). Promise.allSettled([fetchActivity(id), fetchComments(id)]). Время параллельной загрузки = max(100, 200, 150) = 200мс, последовательной = 450мс.