При решении задач про промисы советую подглядывать в эту шпаргалку. Тут в формате разоблачения мифов и ответов на вопросы я описал основные ситуации, которые происходят с промисами.
На собеседовании можно сказать, что промис — это экземляр класса Promise. А дальше описать, как этот класс устроен. Для того, чтобы описать какой-то класс, достаточно описать его внутреннее и внешнее устройство.
Внутри промиса хранятся: статус промиса, значение и два набора функций-подписчиков (первый набор на случай успешного завершения и второй на случай неудачи).
pending, fulfilled и rejected.pending в fulfilled или из статуса pending в rejected, других вариантов нет.pending промис не хранит никакое значение. В состоянии fulfilled он хранит value. В состоянии rejected хранит reason.value или reason нельзя.Снаружи у промиса доступны: конструктор и три метода then/catch/finally, а также несколько статических методов (например, Promise.all).
then. С помощью него мы можем «подписаться» на событие изменения статуса промиса.then принимает два аргумента-колбэка. Первый попадает в набор функций для выполнения в случае успеха, а второй — в набор на случай неудачи.catch и finally фактически являются обертками на then и служат только для написания более лаконичного кодаРассмотрим код:
Promise.resolve(1) .then(x => x + 1) .catch(x => x + 2) .then(x => x + 3)
В этом коде создается 4 промиса! Все они создаются синхронно!
После выполнения этого кода есть один промис в состоянии fulfilled (потому что Promise.resolve, если туда передать не thenable объект, всегда возвращает fulfilled промис).
И есть еще три промиса, находящихся в состоянии pending. Методы then/catch/finally всегда возвращают промис в сотоянии pending. Даже если промис, на котором они вызываются, находится в завершенном состоянии.
Код внутри then никогда не выполняется синхронно!
Рассмотрим код:
p.then(cb)
Если p находится в состоянии pending, то в момент изменения статуса промиса p с pending на fulfilled функция cb отправится в очередь микротасков. Когда осовободится колстек и когда дойдет очередь до этой таски, мы достанем ее, положим на колстек и выполним.
Менее известный факт: даже если p находится в состоянии fullfilled, функция cb все равно не выполнится сразу. Она отправится в очередь микротасков. Но она не выполнится синхронно. Сначала она попадает в очередь, далее мы достаем из очереди все, что там лежало до нее, и только потом достаем ее.
Рассмотрим код, где на promise1 мы подписываемся через метод then, а на promise2 через метод catch.
const result1 = promise1.then(cb1, cb2); const result2 = promise2.catch(cb2);
От состояния promise1/promise2 зависит только то, в какой коллбэк мы пападем. Состояние промиса result1/result2 (который возвращается then или catch) не зависит от состояния promise1/promise2!
promise1 fulfilled, то мы выполняем cb1 (далее см. п. 5)promise1 rejected, то мы выполняем cb2 (далее см. п. 5)promise2 rejected, то мы выполняем cb2 (далее см. п. 5)promise2 fulfilled, то сb1 и cb2 не выполняютсяДалее в п. 5 мы увидим, как определяется статус промиса в зависимости от колбэков cb1 и cb2. Но что происходит в последнем случае, когда колбэки вообще не вызываются?
Рассмотрим код
promise1.then(val1, val2) promise2.catch(val2)
Пусть в метод then промиса promise1 мы передали не функцию (а массив, строку, число, другой промис или что-то еще). Кстати, если мы вызвали then с одним аргументом, то это то же самое, что передать вместо val2 undefined.
В этом случае в зависимости от аргумента это значение, которое не функция, заменяется на функцию.
val1 заменяется на x => xval2 заменяется на x => { throw x }Например, код
Promise.reject(5).then(x => x + 1)
эквивалентен коду
Promise.reject(5).then(x => x + 1, x => { throw x })
То же самое происходит с catch. Так как мы указываем только второй колбэк (формально он первый и единственный, но фактически это аналог второго колбэка then), то первый мы не указываем вообще. Можно сказать, что val1 для catch это undefined. А это значит, что этот колбэк заменяется на x => x.
Рассмотрим код
const result = promise.catch(cb)
В случае, когда промис promise находится в статусе fulfilled со значаением value, он возвращает новый промис result, который тоже находится в статусе fulfilled со значением value.
Посмотрим еще раз на код:
const result1 = promise1.then(cb1, cb2); const result2 = promise2.catch(cb2);
После того как мы попали в cb1 или cb2 нам уж не важно как мы туда попали! Состояние promise1/promise2 вообще никак не влияет на состояние result1/result2. Единственное, что влияет — что происходит в колбэке cb1/cb2.
Еще раз напомню, если cb1/cb2 не передан или является не функцией, то происходит автоматическая замена на соответствующую функцию (см. п. 4).
После этого состояние result1/result2 определяется исключительно поведением внутри колбэка, который мы вызвали (как мы выбрали этот колбэк — см п. 3).
С точки зрения алгоритма ниже все эти ситуации рассматриваются одинаково
const result = promise.then(cb); const result = promise.then(×××, cb); const result = promise.catch(cb);
Изначально result всегда находится в состоянии pending, но дальше:
cb возвращается обычное значение value (не промис) или не возвращается ничего (то есть фактически value равно undefined), то промис result становится fulfilled со значением value.cb бросается значение reason (throw reason), то промис result становится rejected со значением reason.cb возвращается промис, то промис result получает то же самое значение и тот же самый статус, что этого возвращаемого промиса внутри cb.p1.then(cb1).catch(cb2); // #1 p1.then(cb1, cb2); // #2
Эти две строчки похожи, но работают по-разному! Если вы хотите обработать обе ситуации — успешное и неуспешное завершение — используйте всегда второй вариант.
Метод catch следует использовать только в двух случаях:
cb2 обрабатывает случай режджекта p1 и ошибку в cb1. В примере #2 cb2 обрабатывает только случай режджекта p1.В п. 2 я начал с того, что then/catch/finally всегда возвращают промис в состоянии pending. Конструктор Promise может возвращать промис, который сразу находится в терминальном состоянии (fulfilled или rejected).
console.log(1); const promise = new Promise(resolve => { console.log(2); resolve("hello"); console.log(3); }); console.log(4); console.log(promise);
Цифры 1, 2, 3, 4 выведутся по порядку. Промис promise в момент вызова resolve меняет статус. То еще до вывода числа 3 он станет fulfilled. Если мы его выедем сразу после промиса в консоль, то увидим его значение. Если бы он становился fulfilled как-то асинхронно, то есть проходя через очередь тасок, то в синхронном коде мы бы увидели pending.
Также рекомендую прочитать 4 статьи на MDN: про промисы в целом, про методы then и catch и про конструктор. Ниже я собрал самую важную информацию из этих разделов.