Цепочки промисов
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
У промисов есть свойство, ради которого их и придумали: then возвращает новый промис. Благодаря ему асинхронные шаги выстраиваются в ряд — в цепочку, которую читаешь сверху вниз, как обычный список дел.
Главное про then: он не отдаёт вам старый промис обратно. Он создаёт новый и возвращает его. Поэтому вызовы можно ставить друг за другом — каждый следующий then подписывается не на исходный промис, а на результат предыдущего шага.
Новый промис выполнится тем, что вернул обработчик. Вернули число — промис завершится числом, вернули строку — строкой. Значение течёт по цепочке сверху вниз, и каждый шаг получает результат предыдущего в аргументе.
Внутри обработчика есть ровно два интересных варианта. Вернуть обычное значение — следующий then получит его почти сразу. Вернуть промис — цепочка встанет на паузу, дождётся его выполнения и передаст дальше его результат, а не сам промис.
Эта пауза — вся суть цепочек. Асинхронный шаг не выпадает из последовательности: он просто занимает в ней своё место, а следующий обработчик спокойно ждёт своей очереди. Разворачивание промиса делает движок, вам не нужно ни писать while, ни подписываться вручную.
Промисы можно вкладывать друг в друга — и тогда получится ровно та лестница, ради ухода от которой их придумали. Вот как выглядит цепочка, написанная «по-колбэчному».
Результат одинаковый, но разница не только в отступах. У плоской цепочки один общий хвост: любой шаг можно вставить, убрать или поменять местами, и один catch в конце ловит ошибку с любого уровня. В лестнице каждая ветка живёт своей жизнью, и ошибку из глубины наружу приходится тащить руками.
Ошибка в любом обработчике не «взрывается» на месте — она превращается в отклонённый промис и едет по цепочке дальше, пропуская все then, пока не встретит catch.
Даже если промис уже выполнен, обработчик из then не вызовется прямо сейчас. Он встанет в очередь микрозадач и дождётся, пока доработает текущий синхронный код. Это то самое правило из статьи про промисы: исключений у него нет, и на нём ломается половина «загадочных» выводов в консоли.
Внутри одной цепочки шаги идут строго по очереди: следующий обработчик не запустится, пока не выполнился промис предыдущего. А две независимые цепочки продвигаются вперемешку — по шагу от каждой.
Обычное значение попадает в следующий шаг на ближайшем проходе очереди. А возвращённый промис движок должен сначала «развернуть»: подписаться на него и дождаться результата. Это добавляет два лишних прохода, и соседняя цепочка успевает обогнать.
Помнить это наизусть не нужно — важен вывод: точный порядок между разными цепочками не стоит закладывать в логику. Внутри одной цепочки порядок гарантирован, между ними — нет.
Стрелка без фигурных скобок возвращает значение сама. Как только появляются фигурные скобки, возвращать нужно руками — и вот здесь промис теряется. Обработчик отработает, вернёт undefined, и цепочка поедет дальше, не дожидаясь ничего.
Увидели фигурные скобки внутри then — глазами ищите return. Девять из десяти «цепочка не дождалась» — это он.
- then всегда возвращает новый промис — поэтому вызовы складываются в цепочку, а не переписывают один и тот же объект.
- Вернули из обработчика обычное значение — следующий шаг получит его; вернули промис — цепочка дождётся его результата.
- Вложенные then воссоздают лестницу колбэков. Плоская цепочка читается сверху вниз, и один catch в конце ловит ошибку любого шага.
- Обработчики then — микрозадачи: сначала выполняется весь синхронный код, и только потом они. Внутри цепочки порядок строгий, между разными цепочками — нет.
- Фигурные скобки без return обрывают передачу: следующий шаг мгновенно получает undefined вместо ожидаемого значения.
1/3Что выведет код: Promise.resolve(5).then((x) => x + 1).then((x) => console.log(x));
2/3Обработчик написан так: .then(() => { загрузка(); }), где загрузка() возвращает промис. Что получит следующий then?
3/3Какой порядок вывода: console.log("A"); Promise.resolve().then(() => console.log("B")); console.log("C");
Разгладить лестницу
Код работает, но написан вложенно. Перепишите его в плоскую цепочку из трёх then так, чтобы вывод не изменился.
Каждый вложенный вызов превращается в отдельный шаг: обработчик возвращает промис, и цепочка сама его дожидается.
Ключевая замена: там, где было { wait(...).then(...) }, стало => wait(...) без фигурных скобок. Отступы перестали расти, а в конец теперь можно добавить один общий catch.
Куда делось значение
Сначала предположите, что выведет этот код, а потом почините его так, чтобы в консоли оказалось 11.
Код выведет NaN. Первый обработчик написан с фигурными скобками и без return, поэтому он возвращает undefined; промис из wait просто повисает в стороне. Дальше считается undefined + 1, а это NaN.
Теперь второй шаг ждёт результат wait и получает 10, а третий превращает его в 11. Заодно исчезли лишние фигурные скобки — с ними ошибку легко нажить снова.