В каком порядке выполняются промисы и их обработчики then/catch в JavaScript и как это влияет на порядок ошибок?
Что проверяют
Знаете ли вы, как промисы ложатся на цикл событий: что выполняется синхронно, что уходит в очередь микрозадач и как по цепочке путешествует ошибка. Следом почти всегда дают кусок кода с setTimeout, Promise.resolve() и async/await и просят назвать порядок вывода.
Как отвечать
Базовые правила:
- исполнитель синхронный: функция внутри
new Promise(...)выполняется сразу, до строки, следующей за конструктором; - обработчики всегда асинхронны: колбэк
thenилиcatchпопадает в очередь микрозадач, когда промис выполнен или отклонён, даже если это случилось до вызоваthen; - порядок очередей: сначала весь синхронный код, затем очередь микрозадач целиком, включая добавленные по ходу, и только потом следующая макрозадача вроде
setTimeout. ПоэтомуPromise.resolve().then(a)сработает раньшеsetTimeout(b, 0); - каждый
thenвозвращает новый промис: исключение в обработчике или возврат отклонённого промиса отклоняют его, и следующиеthenпропускаются до ближайшегоcatch; catch(fn)этоthen(undefined, fn): после него цепочка продолжается как выполненная, еслиfnне бросил ошибку заново.
Отсюда поведение ошибок. then(onOk, onErr) не поймает исключение, брошенное в onOk того же вызова, нужен catch дальше по цепочке. Две независимые цепочки на уже выполненных промисах чередуются по одному шагу: у p1.then(A).then(C) и p2.then(B).then(D) порядок будет A, B, C, D. Возврат промиса из then добавляет лишние такты. Promise.all отклоняется первой по времени ошибкой, остальные теряются, поэтому для сбора всех ошибок нужен Promise.allSettled. О необработанном отклонении браузер сообщает событием unhandledrejection.
Где спотыкаются
Считают код в new Promise асинхронным. Из-за этого ставят лог из исполнителя после синхронных, хотя он выводится раньше.
Думают, что then и catch встают в очередь в момент объявления. Обработчик попадает в очередь, только когда промис перешёл в итоговое состояние.
Ждут, что try/catch вокруг цепочки без await поймает отклонение. Синхронный блок к этому моменту уже завершён, ошибку ловит только catch цепочки или await внутри try.