Урок по треку JavaScript 8 мин

Промисы и async/await за 8 минут

Состояния промиса, цепочки, комбинаторы, ошибки и параллельность.

Промис это объект-обещание результата асинхронной операции. Он появился, чтобы заменить вложенные колбэки («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 и что будет с необработанным отклонением.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.

Вопросы модуля (36)