Асинхронный JavaScript
JavaScript исполняется в одном потоке, поэтому «долгая» работа — запрос к серверу, таймер, чтение файла — не может просто заблокировать выполнение и подождать. Вместо этого вы описываете, что сделать, когда результат будет готов, а движок продолжает крутить остальной код. Исторически это выражали колбэками, затем промисами, а сегодня — синтаксисом async/await, который поверх тех же промисов читается как обычный последовательный код.
Главная ловушка темы — думать, что асинхронность означает параллельные потоки. Это не так: один стек, одна очередь, всё по очереди. await не останавливает поток, а приостанавливает лишь свою функцию и уступает управление. Промис разрешается ровно один раз и дальше неизменяем. Отклонение — это сохранённая причина, а не выброшенный стек. Ниже — слои, разбирающие каждый механизм от колбэка до push-потока observable.
Карта темы
- Колбэки — функция, переданная аргументом для вызова позже, соглашение error-first и почему вложенность рождает callback hell.
- Промисы — объект будущего результата, три состояния, одноразовое разрешение и цепочки
.then. - Комбинаторы Promise —
all,race,allSettled,anyи то, как каждый реагирует на успех и отказ. - async / await — сахар над промисами, приостановка функции и возобновление через микрозадачу.
- Обработка ошибок —
try/catchвокругawait,throwкак reject и как теряются необработанные отклонения. - Определение промиса — утиная типизация thenable вместо
instanceofи какPromise.resolveперенимает состояние. - Observable — ленивый push-поток из многих значений против нетерпеливого одноразового промиса.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Считать, что await блокирует поток | Ложная модель — приостанавливается лишь функция, поток свободен |
| Думать, что колбэк всегда асинхронен | Многие API (Array.map, некоторые библиотеки) вызывают колбэк синхронно |
| Ждать, что разрешённый промис сменит состояние | Разрешение одностороннее и финальное, значение зафиксировано навсегда |
Забыть return внутри .then | Поток значений в цепочке рвётся, следующий обработчик получит undefined |
Опираться на instanceof Promise | Ломается между realm'ами (iframe, воркеры) — нужна утиная типизация .then |
Не сделать await или .catch промису | Отклонение становится unhandled rejection и молча теряется |
Путать race и any | race — первый разрешившийся в любую сторону, any — первый успешный |
Значение для собеседований
Асинхронность — самый частый блок вопросов на middle/senior-фронтенд. Интервьюер проверяет не знание синтаксиса, а модель исполнения: понимаете ли вы, что поток один, что await — это точка приостановки с продолжением-микрозадачей, и что промис — неизменяемый одноразовый контейнер результата.
Что обычно проверяют:
- Три состояния промиса и то, что разрешение финально и однократно.
- Что
async-функция всегда возвращает промис, аawaitприостанавливает только её. - Разницу четырёх комбинаторов и их реакцию на первый отказ.
- Как ловить и как теряются async-ошибки, что такое unhandled rejection.
- Чем observable (ленивый, многозначный, отменяемый) отличается от промиса.
Типичный неверный ответ: «await останавливает всю программу, пока не придёт значение». На самом деле await приостанавливает лишь текущую async-функцию, возвращает управление вызывающему, а продолжение планируется микрозадачей — весь остальной синхронный код успевает отработать раньше.