Промис это объект-обещание результата асинхронной операции. Он появился, чтобы заменить вложенные колбэки («callback hell») цепочками, а async/await сделал эти цепочки похожими на обычный синхронный код.
Состояния
У промиса три состояния: pending (ожидание), fulfilled (выполнен со значением), rejected (отклонён с причиной). Переход происходит один раз: повторные resolve и reject игнорируются. Выполненный или отклонённый промис называют settled.
const p = new Promise((resolve, reject) => {
// executor выполняется синхронно, прямо в конструкторе
setTimeout(() => resolve(42), 100);
});
Если executor бросит исключение, промис станет rejected.
then, catch, finally
then(onFulfilled, onRejected) всегда возвращает новый промис. Его судьба зависит от того, что сделал обработчик:
- вернул значение: новый промис выполнен этим значением;
- вернул промис (или thenable): новый промис «следует» за ним;
- бросил исключение: новый промис отклонён.
catch(fn) это then(undefined, fn). После catch, который вернул значение, цепочка снова в состоянии успеха. finally(fn) не получает аргументов и не меняет результат цепочки, если только сам не бросит ошибку или не вернёт отклонённый промис.
Обработчики всегда вызываются асинхронно, микрозадачей, даже если промис уже выполнен.
Типичные ошибки в цепочках
// 1. Забыли return: следующий then получит undefined и не дождётся запроса
getUser().then((u) => { fetchOrders(u.id); }).then((orders) => ...);
// 2. Вложенные then вместо цепочки
getUser().then((u) => getOrders(u.id).then((o) => ...));
// 3. catch в середине глотает ошибку, и дальше цепочка идёт как успешная
Комбинаторы
| Метод | Когда выполнится | Когда отклонится |
|---|---|---|
Promise.all | все выполнены, массив результатов в исходном порядке | при первой ошибке |
Promise.allSettled | когда все завершатся, массив {status, value / reason} | никогда |
Promise.race | первый завершившийся (успех или ошибка) | если первый завершился ошибкой |
Promise.any | первый успешный | если все отклонены, AggregateError |
Важно: Promise.all при ошибке не отменяет остальные операции, они продолжают выполняться, просто их результат никому не нужен.
async/await
async-функция всегда возвращает промис. return x выполняет его значением x, throw отклоняет. await приостанавливает функцию до выполнения промиса и возвращает его значение или бросает его ошибку, которую ловит обычный try/catch.
async function loadProfile(id) {
try {
const user = await api.getUser(id);
const posts = await api.getPosts(user.id);
return { user, posts };
} catch (err) {
console.error(err);
throw new Error('Профиль не загрузился', { cause: err });
}
}
Последовательно или параллельно
// последовательно: 1 с + 1 с = 2 с
const a = await load('a');
const b = await load('b');
// параллельно: около 1 с
const [a2, b2] = await Promise.all([load('a'), load('b')]);
Запросы стартуют в момент вызова функции, а не в момент await. Если операции независимы, запускайте их вместе.
await внутри forEach не ждёт: forEach не знает о промисах. Для последовательной обработки нужен for...of с await, для параллельной Promise.all(items.map(...)).
Необработанные отклонения
Если отклонённый промис остался без обработчика, браузер генерирует событие unhandledrejection, а Node.js начиная с 15 версии завершает процесс. Всегда завершайте цепочку catch или оборачивайте await в try/catch.
Отмена
Промис сам по себе отменить нельзя. Для отмены операций используют AbortController: его signal передают в fetch и свои функции, а AbortSignal.timeout(ms) создаёт сигнал с таймаутом.
Что спросят на собесе
- Какие состояния есть у промиса и может ли он сменить состояние дважды.
- Чем отличаются
all,allSettled,race,any. - Что вернёт
then, если обработчик вернул значение, промис или бросил ошибку. - Как выполнить запросы параллельно и почему
awaitвforEachне работает. - Как обработать ошибку в
async/awaitи что будет с необработанным отклонением.