Что такое промисы и чем async/await отличается от цепочек then?
Короткий ответ
- Промис — объект-обёртка над будущим результатом операции
- Три состояния: pending, fulfilled, rejected; переход необратим
- then возвращает новый промис, поэтому цепочки работают
- async-функция всегда возвращает промис
- await приостанавливает функцию, не блокируя поток
- Ошибки ловятся через catch или try/catch с await
- Параллельность: Promise.all, allSettled, race, any
async/await — синтаксис поверх тех же промисов, который делает асинхронный код линейным и позволяет обрабатывать ошибки привычным try/catch.
Как сказать вслух
пример ответаПромис — это объект, который представляет результат операции, которая завершится позже: запрос к серверу, таймер. У него есть состояния — ожидание, успех и ошибка. async/await — это не другой механизм, а удобная запись тех же промисов: await ждёт результат, но поток при этом не блокируется, движок продолжает обрабатывать другие события. Ошибки при этом ловятся обычным try/catch, что сильно упрощает чтение кода.
Подробный ответ
Основной ответ
Промис стартует в состоянии pending и один раз переходит либо в fulfilled со значением, либо в rejected с причиной. Подписка через then/catch/finally возвращает новый промис, что позволяет строить цепочки и трансформировать результат. async/await — синтаксический сахар: async-функция возвращает промис, а await разворачивает значение промиса, приостанавливая выполнение функции до его завершения; продолжение ставится в очередь микрозадач. Главные выигрыши — линейный код без вложенных колбэков, обычный try/catch/finally для ошибок, понятные условия и циклы вокруг асинхронных вызовов. Важно не терять параллелизм: последовательные await независимых запросов замедляют код, независимые операции запускают сразу и ждут через Promise.all, а частичные отказы обрабатывают через allSettled.
Ключевые моменты
- Один переход состояния. Промис нельзя «перерешать»: после fulfilled или rejected результат зафиксирован навсегда.
- await и поток. await приостанавливает только текущую async-функцию; главный поток свободен для других задач.
- Обработка ошибок. Непойманный reject даёт unhandledrejection; нужен catch в конце цепочки или try/catch вокруг await.
- Комбинаторы. Promise.all падает при первой ошибке, allSettled ждёт всех, race и any — про первый результат.
Практический контекст
Это ежедневный инструмент: запросы через fetch, загрузка данных в эффектах, очереди операций. На собеседовании любят спрашивать разницу между последовательными await и Promise.all, а также что произойдёт с ошибкой без catch. Хороший кандидат упоминает и отмену через AbortController, потому что сами промисы отменять нельзя — это частая реальная задача при переключении между экранами.
Пример кода
// Медленно: запросы идут по очереди
const a = await fetchUser(1);
const b = await fetchUser(2);
// Быстро: запускаем сразу, ждём вместе
const [u1, u2] = await Promise.all([fetchUser(1), fetchUser(2)]);Частые ошибки
- Пишут await в цикле для независимых запросов, теряя параллельность
- Считают, что await блокирует весь поток, а не только текущую функцию
- Забывают обрабатывать reject, получая unhandledrejection в проде