Промисы в JavaScript — это как обещания в жизни: ты не знаешь, когда получишь результат, но точно знаешь, что он будет. Если вы уже умеете создавать промисы и обрабатывать их через .then(), пора копнуть глубже. В этой статье разберём продвинутые приёмы, которые сэкономят вам время и нервы.
Promise.all: когда нужно всё и сразу
Представьте, что вы готовите ужин из трёх блюд. Можно варить суп, жарить мясо и печь пирог по очереди — долго. А можно запустить все процессы параллельно и ждать, пока всё будет готово. Promise.all делает именно это: принимает массив промисов и возвращает новый промис, который выполнится, когда все исходные промисы завершатся успешно.
Пример: загружаем данные пользователя, его заказы и настройки одновременно.
const userPromise = fetch('/api/user/1');
const ordersPromise = fetch('/api/orders?user=1');
const settingsPromise = fetch('/api/settings/1');
Promise.all([userPromise, ordersPromise, settingsPromise])
.then(([user, orders, settings]) => {
console.log('Пользователь:', user);
console.log('Заказы:', orders);
console.log('Настройки:', settings);
})
.catch(error => {
console.error('Что-то пошло не так:', error);
});
Если хотя бы один промис отклонится, весь Promise.all сразу перейдёт в состояние rejected. Это как если бы суп пригорел — всё блюдо испорчено.
Promise.all, когда все результаты важны и нужны вместе. Если падение одного не критично — смотрите на Promise.allSettled.
Promise.race и Promise.any: кто быстрее
Иногда нужно не ждать всех, а получить первый результат. Promise.race возвращает промис, который завершается, как только завершится любой из переданных промисов (успешно или с ошибкой). Это гонка: кто первый — того и тапки.
Пример: запрашиваем данные с двух серверов и берём тот, который ответит быстрее.
const server1 = fetch('https://api1.example.com/data');
const server2 = fetch('https://api2.example.com/data');
Promise.race([server1, server2])
.then(response => response.json())
.then(data => console.log('Победитель:', data))
.catch(error => console.error('Все упали:', error));
Promise.any — более мягкий вариант: он ждёт первый успешный промис. Если все отклонятся, возвращается AggregateError со списком ошибок. Это как выбрать лучшего из худших.
const p1 = Promise.reject('Ошибка 1');
const p2 = new Promise(resolve => setTimeout(resolve, 100, 'Успех!'));
const p3 = Promise.reject('Ошибка 2');
Promise.any([p1, p2, p3])
.then(result => console.log(result)) // 'Успех!'
.catch(errors => console.error(errors.errors));
Promise.allSettled: когда важно знать обо всех
Бывает, что нужно дождаться выполнения всех промисов, но неважно, успешны они или нет. Например, вы отправляете несколько запросов на сервер и хотите собрать статистику: сколько успешных, сколько с ошибками. Promise.allSettled возвращает массив объектов с полями status и value (или reason).
const promises = [
fetch('/api/1'),
fetch('/api/2'),
Promise.reject('Сломалось')
];
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Промис ${index} выполнен:`, result.value);
} else {
console.log(`Промис ${index} отклонён:`, result.reason);
}
});
});
Этот метод идеален для сбора логов или когда нужно обработать частичный успех.
Цепочки промисов и обработка ошибок
Цепочки .then() — это классика, но есть нюансы. Если внутри .then() вы бросаете ошибку, она перехватывается следующим .catch(). Если не бросаете — цепочка продолжается. Важно возвращать промисы из .then(), чтобы сохранить последовательность.
function getUser(id) {
return fetch(`/api/user/${id}`).then(res => res.json());
}
function getOrders(userId) {
return fetch(`/api/orders?user=${userId}`).then(res => res.json());
}
getUser(1)
.then(user => {
console.log('Пользователь:', user);
return getOrders(user.id); // возвращаем промис
})
.then(orders => {
console.log('Заказы:', orders);
})
.catch(error => {
console.error('Ошибка в цепочке:', error);
});
Если забыть return, следующий .then() получит undefined и цепочка сломается.
.catch() в конец цепочки, иначе необработанная ошибка может уронить приложение.
Асинхронные функции: async/await поверх промисов
async/await — это синтаксический сахар над промисами. Он делает код похожим на синхронный, но под капотом всё те же промисы. Главное правило: await можно использовать только внутри async-функции.
async function loadUserData(userId) {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
return { user, orders };
} catch (error) {
console.error('Ошибка загрузки:', error);
throw error; // пробрасываем дальше
}
}
loadUserData(1).then(data => console.log(data));
Обратите внимание: await останавливает выполнение функции до разрешения промиса, но не блокирует весь поток JavaScript. Другие задачи продолжают работать.
Если нужно выполнить несколько промисов параллельно, используйте Promise.all вместе с await:
async function loadAll() {
const [user, orders, settings] = await Promise.all([
fetch('/api/user/1').then(r => r.json()),
fetch('/api/orders?user=1').then(r => r.json()),
fetch('/api/settings/1').then(r => r.json())
]);
return { user, orders, settings };
}
Сравнение методов: таблица
Чтобы не запутаться, вот шпаргалка по методам:
| Метод | Когда завершается | Результат | Обработка ошибок |
|---|---|---|---|
| Promise.all | Все успешны | Массив значений | Первый reject |
| Promise.race | Первый завершённый | Значение или ошибка | Первый reject |
| Promise.any | Первый успешный | Значение | AggregateError, если все reject |
| Promise.allSettled | Все завершены | Массив объектов статуса | Нет reject |
Практические советы и лайфхаки
- Не забывайте про return. В цепочках
.then()всегда возвращайте промис или значение, иначе следующий шаг получитundefined. - Используйте async/await для читаемости. Если логика сложная,
async/awaitсtry/catchвыглядит понятнее, чем длинные цепочки. - Promise.all с ограничением параллелизма. Если промисов слишком много, можно разбить их на группы, чтобы не перегружать сервер.
- Обрабатывайте ошибки в каждом промисе. Если один промис в
Promise.allупадёт, остальные всё равно выполнятся, но результат потеряется. Иногда лучше обернуть каждый промис в.catch(), чтобы получить частичный результат.
console.log внутри .then() и .catch(), но не забывайте удалять их в продакшене.
Ещё один приём — Promise.resolve() и Promise.reject() для создания уже разрешённых или отклонённых промисов. Это полезно в тестах или когда нужно вернуть промис из синхронной функции.
function maybeAsync(value) {
if (value > 0) {
return Promise.resolve(value);
} else {
return Promise.reject('Отрицательное значение');
}
}
maybeAsync(5).then(console.log); // 5
maybeAsync(-1).catch(console.error); // 'Отрицательное значение'
Что в итоге
Промисы — мощный инструмент, но их продвинутые методы требуют понимания. Promise.all для параллельных задач, Promise.race для гонок, Promise.any для первого успеха, Promise.allSettled для сбора всех результатов. Не забывайте про async/await для читаемости и всегда обрабатывайте ошибки. Практикуйтесь, и асинхронный код станет вашим вторым языком.
Студия WNDER



