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.

В марте в рамках специализации FullstackDeveloper пройдут два открытых урока, на которые приглашаем всех желающих.
Тема 1: Прототипное наследование в JavaScript. Разберемся, что такое прототипное наследование и как оно может помочь при разработке программ. В результате вы лучше поймете объектную модель Javascript и сможете писать ООП код с экономией памяти. Регистрация
Тема 2: Какими задачами проверяют ваше знание JavaScript? Посмотрим на типы задач, которые могут дать прямо во время интервью. Обсудим, как и что проверяют эти задачи, и что можно сделать, чтобы подготовиться к таким моментам. Записаться
