
Promise — это отличительная особенность JavaScript как асинхронного языка программирования. Нравится вам это или нет, понять его в любом случае придется.
В этой статье я привожу 10 примеров кода с Promise, начиная от базового уровня заканчивая продвинутым. Готовы? Начнем!
Задача №1: Конструктор Promise
Каким будет вывод этого фрагмента кода?
console.log('start'); const promise1 = new Promise((resolve, reject) => { console.log(1) }) console.log('end');
Анализ
С первой задачей справиться легко.
Что мы знаем:
-
Блоки синхронного кода всегда выполняются последовательно сверху вниз.
-
Когда мы вызываем
new Promise(callback), функция коллбэка будет выполнена сразу же.
Результат
Итак, этот код должен последовательно выводить start, 1, end.
console.log('start') const promise1 = new Promise((resolve, reject) => { console.log(1) }) console.log('end');
Задача №2: .then()
Каким будет вывод этого фрагмента кода?
console.log('start'); const promise1 = new Promise((resolve, reject) => { console.log(1) resolve(2) }) promise1.then(res => { console.log(res) }) console.log('end');
Анализ
Это фрагмент асинхронного кода. То есть коллбэк-функция в .then().
Помните о том, что интерпретатор JavaScript всегда сначала выполняет синхронный код, а затем асинхронный.
При столкновении с этой проблемой нам нужно только различать синхронный и асинхронный код.

Результат
console.log('start'); const promise1 = new Promise((resolve, reject) => { console.log(1) resolve(2) }) promise1.then(res => { console.log(res) }) console.log('end');;
Итак, выводом будет start , 1 , end и 2 .
Задача №3: resolve()
Каким будет вывод этого фрагмента кода?
console.log('start'); const promise1 = new Promise((resolve, reject) => { console.log(1) resolve(2) console.log(3) }) promise1.then(res => { console.log(res) }) console.log('end');
Анализ
Этот фрагмент кода почти такой же, как и предыдущий; единственная разница в том, что после resolve(2) есть console.log(3).
Помните, что метод resolve не прерывает выполнение функции. Код, стоящий за ним, по-прежнему будет выполняться.
Результат
Таким образом, выходным результатом будет start , 1 , 3 , end и 2 .
console.log('start'); const promise1 = new Promise((resolve, reject) => { console.log(1) resolve(2) console.log(3) }) promise1.then(res => { console.log(res) }) console.log('end');;
Я неоднократно сталкивался с мнением, будто resolve прервет выполнение функции, поэтому я подчеркиваю этот момент здесь.
Задача №4: resolve() не вызывается
Каким будет вывод этого фрагмента кода?
console.log('start'); const promise1 = new Promise((resolve, reject) => { console.log(1) }) promise1.then(res => { console.log(2) }) console.log('end');
Анализ
В этом коде метод resolve никогда не вызывался, поэтому promise1 всегда находится в состоянии ожидания (pending). Так что promise1.then(…) никогда не выполнялся. 2 не выводится в консоли.
Результат
Выходным результатом станет start , 1 , end .
console.log('start'); const promise1 = new Promise((resolve, reject) => { console.log(1) }) promise1.then(res => { console.log(2) }) console.log('end');;
Задача №5: Нечто, сбивающее с толку
console.log('start') const fn = () => (new Promise((resolve, reject) => { console.log(1); resolve('success') })) console.log('middle') fn().then(res => { console.log(res) }) console.log('end')
Каким будет вывод этого фрагмента кода?
Анализ
Этот код преднамеренно добавляет функцию, чтобы запутать испытуемых, то есть нас, и это fn.
Пожалуйста, помните, что независимо от того, сколько существует слоев вызовов функций, наши базовые принципы остаются неизменными:
-
Сначала выполняется синхронный код, а затем асинхронный.
-
Синхронный код выполняется в том порядке, в котором он был вызван.

Результат
Выходным результатом будет start , middle, 1 , end и success.
console.log('start') const fn = () => (new Promise((resolve, reject) => { console.log(1); resolve('success') })) console.log('middle') fn().then(res => { console.log(res) }) console.log('end');
Задача №6: с Fulfilling Promise
Каким будет вывод этого фрагмента кода?
console.log('start') Promise.resolve(1).then((res) => { console.log(res) }) Promise.resolve(2).then((res) => { console.log(res) }) console.log('end')
Анализ
Здесь Promise.resolve(1) вернет объект Promise, состояние которого fulfilled, а результат равен 1 . Это синхронный код.

Выходным результатом будет start , end , 1 и 2.
console.log('start') Promise.resolve(1).then((res) => { console.log(res) }) Promise.resolve(2).then((res) => { console.log(res) }) console.log('end');
Ну что, думаете, это незначительные трудности?
Это только начало. Сложность Promise проявляется, когда он используется с setTimeout. Следующие задачи будут сложнее.
Готовы? Продолжим.
Задача №7: setTimeout vs Promise
Каким будет вывод этого фрагмента кода?
console.log('start') setTimeout(() => { console.log('setTimeout') }) Promise.resolve().then(() => { console.log('resolve') }) console.log('end')
Анализ
Обратите внимание, это сложный вопрос. Если вы сможете правильно ответить на него и объяснить причину, то можно считать, что ваше понимание асинхронного программирования в JavaScript достигло среднего уровня.
Прежде чем я дам объяснение, давайте вспомним соответствующую теоретическую базу.
Ранее мы говорили, что синхронный код выполняется в порядке вызова, так в каком же порядке выполняются эти асинхронные коллбэк-функции?
Кто-то может сказать, что тот, кто закончит первым, будет и выполнен первым. Что ж, это правда, но что, если две асинхронные задачи выполняются одновременно?
Например, в приведенном выше коде таймер setTimeout равен 0 секундам, а Promise.resolve() также вернет выполненный объект Promise сразу же после выполнения.
Обе асинхронные задачи выполняются немедленно, поэтому чья коллбэк-функция будет выполнена первой?
Некоторые джуны могут сказать, что setTimeout находится в начале, поэтому сначала будет выведен setTimeout, а затем resolve. На самом деле, это утверждение неверно.
Мы знаем, что многие вещи НЕ выполняются в порядке по принципу «первым пришел — первым вышел», например, трафик.
Приоритет
Обычно мы делим весь транспорт на две категории:
-
Общие транспортные средства.
-
Транспортные средства для чрезвычайных ситуаций. Например, пожарные машины и машины скорой помощи.
Чтобы проехать многолюдные перекрестки, мы пропустим первыми пожарные машины и машины скорой помощи. Автомобили скорой помощи имеют приоритет выше, чем другой транспорт. Ключевое слово: приоритеты.

В JavaScript EventLoop также есть понятие приоритета.
-
Задачи с более высоким приоритетом называются микрозадачами. Например:
Promise,ObjectObserver,MutationObserver,process.nextTick,async/await. -
Задачи с более низким приоритетом называются макрозадачами. Например:
setTimeout,setIntervalиXHR.

Хотя setTimeout и Promise.resolve() выполняются одновременно, и даже код setTimeout еще впереди, но из-за низкого приоритета относящаяся к нему коллбэк-функция выполняется позже.

Результат
Выходным результатом будет start , end , resolve и setTimeout.
console.log('start') setTimeout(() => { console.log('setTimeout') }) Promise.resolve().then(() => { console.log('resolve') }) console.log('end');
Задача №8: Микрозадачи смешиваются с макрозадачами
Каким будет вывод этого фрагмента кода?
const promise = new Promise((resolve, reject) => { console.log(1); setTimeout(() => { console.log("timerStart"); resolve("success"); console.log("timerEnd"); }, 0); console.log(2); }); promise.then((res) => { console.log(res); }); console.log(4);
Анализ
Эту задачу легко выполнить, если вы поняли предыдущий код.
Нам просто нужно выполнить эти три шага:
-
Найти синхронный код.
-
Найти код микрозадачи.
-
Найти код макрозадачи.
Сначала выполните синхронный код:

Выведется 1 , 2 и 4 .
Затем выполните микрозадачу:

Но вот ловушка: поскольку текущий Promise все еще находится в состоянии ожидания (pending), код в данный момент выполняться не будет.
Затем выполните макрозадачу:

И состояние promise становится fulfilled .
Затем с помощью Event Loop снова выполните микрозадачу:

const promise = new Promise((resolve, reject) => { console.log(1); setTimeout(() => { console.log("timerStart"); resolve("success"); console.log("timerEnd"); }, 0); console.log(2); }); promise.then((res) => { console.log(res); }); console.log(4);;
Задача №9: приоритезировать микрозадачи и макрозадачи
Прежде чем мы определим приоритет между микрозадачами и макрозадачами, здесь мы рассмотрим случай поочередного выполнения микрозадач и макрозадач.
Что выводит этот фрагмент кода?
const timer1 = setTimeout(() => { console.log('timer1'); const promise1 = Promise.resolve().then(() => { console.log('promise1') }) }, 0) const timer2 = setTimeout(() => { console.log('timer2') }, 0)
Анализ
Некоторые могут подумать, что микрозадачи и макрозадачи выполняются так:
-
Сначала выполняются все микрозадачи.
-
Выполняются все макрозадачи.
-
Выполняются все микрозадачи снова.
-
Цикл повторяется / Цикл завершается.
Но это утверждение неверно. Правильно вот так:
-
Сначала выполняются все микрозадачи.
-
Выполняется одна макрозадача.
-
Повторно выполняются все (вновь добавленные) микрозадачи.
-
Выполняется следующая макрозадача.
-
Цикл повторяется / Цикл завершается.
Так:

Или вот так:

Таким образом, в приведенном выше коде коллбэк-функция Promise.then будет выполняться перед коллбэк-функцией второго setTimeout, потому что это микрозадача, и она была врезана в последовательность задач.

Результат

Задача №10: типичный вопрос с собеседования
Что ж, это наша последняя задача. Если вы сможете правильно определить вывод этого кода, то ваше понимание Promise уже на высоком уровне. И однотипные вопросы на собеседовании точно не станут для вас трудностью.
Что выводит этот фрагмент кода?
console.log('start'); const promise1 = Promise.resolve().then(() => { console.log('promise1'); const timer2 = setTimeout(() => { console.log('timer2') }, 0) }); const timer1 = setTimeout(() => { console.log('timer1') const promise2 = Promise.resolve().then(() => { console.log('promise2') }) }, 0) console.log('end');
Анализ
Эта задача является более суровой версией предыдущей задачи, но основной принцип остается прежним.
Вспомните, что мы узнали ранее:
-
Синхронный код
-
Все микрозадачи
-
Первая макрозадача
-
Все недавно добавленные микрозадачи
-
Следующая макрозадача
…
Итак:
-
Выполним весь синхронный код:

-
Выполним все микрозадачи:

-
Выполним первую макрозадачу:

Примечание. На этом шаге макрозадача добавляет в очередь задач новую микрозадачу.
4. Выполним все вновь добавленные микрозадачи:

5. Выполним следующую макрозадачу:

Результат
Вывод будет таким.
console.log('start'); const promise1 = Promise.resolve().then(() => { console.log('promise1'); const timer2 = setTimeout(() => { console.log('timer2') }, 0) }); const timer1 = setTimeout(() => { console.log('timer1') const promise2 = Promise.resolve().then(() => { console.log('promise2') }) }, 0) console.log('end');;
Заключение
Для всех подобных вопросов нужно просто запомнить три правила:
-
Интерпретатор JavaScript всегда сначала выполняет синхронный код, а затем асинхронный.
-
Микрозадачи имеют приоритет над макрозадачами.

3. Микрозадачи могут врезаться в последовательность выполнения в Event Loop.

Завтра состоится открытый урок «Флексы и гриды — в чем отличие?». На вебинаре на практике рассмотрим два популярные способа расположения элементов макета. Рассмотрим их плюсы и минусы, разные примеры применения. Регистрация по ссылке.
ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/686670/
Добавить комментарий