Обещания в JS. Еще один вопрос на собеседовании
Уже есть множество статей, объясняющих, как промисы работают в JavaScript и почему так важно их понимать. Этот пост не предназначен для повторения теории еще раз, а для того, чтобы рассмотреть несколько сложных вопросов, которые вам могут задать на собеседовании.
Эта проблема
Недавно мне выдали такой код:
И вопросы были:
- объясните порядок выполнения каждого варианта
- что, если в цепочку будет добавлено больше предметов
- какие обещания эквивалентны
Решение
Итак, начнем с написания возможной реализации функций foo
и bar
:
Как вы могли заметить, console.log
s много, они должны помочь нам понять порядок выполнения в каждом случае. Кроме того, обратите внимание, что foo
обещание разрешается через ~ 1 с, а bar
- через ~ 3 секунды.
Теперь я хотел бы рассмотреть каждый случай отдельно и ответить на первые два вопроса. Чтобы решить второй вопрос (о более длинной цепочке обещаний), я собираюсь добавить второй then
к каждому варианту.
Первый случай
Начнем с первого случая, который довольно обычен в нашей повседневной жизни:
Теперь, построчно, мы можем предсказать порядок, в котором будет выполняться код:
- Мы вызываем
foo
, который возвращает намPromise
. - Затем внутри тела
new Promise()
мы вызываемsetTimeout
, который разрешитPromise
за ~ 1 с. - После выполнения
Promise
мы переходим к первому телуthen
сres
равным"foo resolved"
и вызываемbar
. - Затем мы повторяем шаги №1–3 с
bar
. - Через ~ 3 с вызывается второе тело
then
сres
равным"bar resolved"
.
Чтобы убедиться, что все вышеперечисленное правильно, вы можете запустить код в консоли, и результат должен выглядеть следующим образом:
foo foo timeout before foo timeout after foo timeout // after ~1s inside then 1: foo resolved // after ~1s bar bar timeout before bar timeout after bar timeout // after ~3s inside then 2: bar resolved // after ~3s
Вот и все! Должно быть довольно просто, если вы раньше работали с Promises.
Второй случай
Случай №2 абсолютно идентичен предыдущему и, по сути, это всего лишь ярлык. Вот почему я пропущу это. Однако вы можете запустить код и сравнить результат с номером 1.
Третий случай
А теперь самый нетривиальный случай (по крайней мере, для меня):
Честно говоря, я не знал, что именно произойдет, если bar
вернет что-то, кроме функции. Итак, мне пришлось проверить MDN:
Если один или оба аргумента опущены или предоставлены без функций, тогда
then
будет отсутствовать обработчик (-ы), но не будет генерировать никаких ошибок. ЕслиPromise
, который вызываетсяthen
, принимает состояние (fulfillment
илиrejection
), для которогоthen
не имеет обработчика, создается новыйPromise
без дополнительных обработчиков, просто принимая конечное состояние исходногоPromise
, в котором был вызванthen
.
Итак, когда bar
возвращает функцию, это будет обработчик для then
. В противном случае этот then
будет пропущен, а следующий в цепочке получит результат выполнения foo
.
Предполагая реализацию bar
, как показано выше, которая возвращает aPromise
, мы можем придумать следующий порядок выполнения:
- Мы вызываем
foo
, который возвращаетPromise
. - Сразу после этого мы вызываем
bar
, который возвращаетPromise
. - После выполнения
Promise
изfoo
(через ~ 1 с) мы переходим к первомуthen
. Однакоbar
вернулPromise
, а не функцию, поэтому мы пропускаем его и переходим сразу ко второмуthen
. - Ввод для второго
then
поступает непосредственно изfoo
, потому что мы пропустили 1-йthen
. - Спустя ~ 3 секунды ошибка
bar
:Promise
была решена.
В итоге мы получим следующий результат:
foo foo timeout before foo timeout after bar bar timeout before bar timeout after foo timeout // after ~1s inside then 2: foo resolved // after ~1s bar timeout // after ~3s
Четвертый случай
Этот случай очень похож на первый, за исключением одной детали - мы не возвращаем результат bar()
в первом then
:
И эта тонкая разница все меняет:
- Мы вызываем
foo
, который возвращаетPromise
. - Через ~ 1 с он разрешается, и мы входим в обработчик first
then
. - Там мы вызываем функцию
bar
. Однако, как только мы вернемPromise
из функции, мы перейдем непосредственно ко второмуthen
. Мы не ждем выполненияbar
’sPromise
. - Поскольку у нас нет явного
return
оператора в первомthen
обработчике, мы возвращаемundefined
, который становится входом для второго обработчикаthen
. - Через ~ 3 с мы разрешили
bar
иPromise
.
В итоге мы получим следующий результат:
foo foo timeout before foo timeout after foo timeout // after ~1s inside then 1: foo resolved // after ~1s bar bar timeout before bar timeout after inside then 2: undefined // after ~1s bar timeout // after ~3s
Заключение
Наконец, мы можем сравнить результаты в каждом случае и сказать, какие из них эквивалентны:
- 1-й и 2-й идентичны, разница только в стиле
- 3-й и 4-й в целом ведут себя по-разному, но есть и общая часть - они не ждут, пока
bar
будетPromise
.
Надеюсь, этот небольшой пост помог вам лучше понять, как Promises работают в JavaScript, и еще один вопрос собеседования будет легко пройден.