[Перевод] 10 задач с JavaScript Promise для подготовки к собеседованиям

a4a84618cb865ebd64a9dcf6956d3814.png

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 всегда сначала выполняет синхронный код, а затем асинхронный.

При столкновении с этой проблемой нам нужно только различать синхронный и асинхронный код.

17da9e93f5a20798ba7a86cccf42e180.png

Результат

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.

Пожалуйста, помните, что независимо от того, сколько существует слоев вызовов функций, наши базовые принципы остаются неизменными:

  • Сначала выполняется синхронный код, а затем асинхронный.

  • Синхронный код выполняется в том порядке, в котором он был вызван.

12388f427cffabb6fc574bbd2597dd1e.png

Результат

Выходным результатом будет 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 . Это синхронный код.

254f1457848db4ea409658bac00edbb0.png

Выходным результатом будет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. На самом деле, это утверждение неверно.

Мы знаем, что многие вещи НЕ выполняются в порядке по принципу «первым пришел — первым вышел», например, трафик.

Приоритет

Обычно мы делим весь транспорт на две категории:

  • Общие транспортные средства.

  • Транспортные средства для чрезвычайных ситуаций. Например, пожарные машины и машины скорой помощи.

Чтобы проехать многолюдные перекрестки, мы пропустим первыми пожарные машины и машины скорой помощи. Автомобили скорой помощи имеют приоритет выше, чем другой транспорт. Ключевое слово: приоритеты.

629fdadef9f9aa1977f84b463f247199.png

 В JavaScript EventLoop также есть понятие приоритета.

  • Задачи с более высоким приоритетом называются микрозадачами. Например: Promise, ObjectObserver, MutationObserver, process.nextTick, async/await.

  • Задачи с более низким приоритетом называются макрозадачами. Например: setTimeout, setInterval и XHR.

0252f123b11ef7a3278dc8a60f55d1f9.png

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

b2327ead6dd93cab59ee381fde497430.png

Результат

Выходным результатом будет 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. Найти код микрозадачи.

  3. Найти код макрозадачи.

Сначала выполните синхронный код:

6559889c8db17c730ccde84efcdc79a3.png

Выведется 1, 2  и 4 .

Затем выполните микрозадачу:

0202e262cccda3e560799aeac61c8844.png

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

Затем выполните макрозадачу:

c12d19a1082cea6b8048fdf8ab03613c.png

И состояние promise становится fulfilled .

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

3ec50b6e08ee283ddd9563086b4754fa.png

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)

Анализ

Некоторые могут подумать, что микрозадачи и макрозадачи выполняются так:

  1. Сначала выполняются все микрозадачи

  2. Выполняются все макрозадачи 

  3. Выполняются все микрозадачи снова

  4. Цикл повторяется / Цикл завершается

Но это утверждение неверно. Правильно вот так:

  1. Сначала выполняются все микрозадачи

  2. Выполняется одна макрозадача

  3. Повторно выполняются все (вновь добавленные) микрозадачи

  4. Выполняется следующая макрозадача

  5. Цикл повторяется / Цикл завершается

Так:

1a62c0af08cf310ca52b1dec78e79fd1.png

Или вот так:

8e411d8773107dca77978aebfa467d1f.png

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

d0950ecf5a43fd6c4c3a50f404e6ab06.png

Результат

fd061e986d287745dc08010356ec92c8.png

Задача №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');

Анализ

Эта задача является более суровой версией предыдущей задачи, но основной принцип остается прежним.

Вспомните, что мы узнали ранее:

  1. Синхронный код

  2. Все микрозадачи

  3. Первая макрозадача

  4. Все недавно добавленные микрозадачи

  5. Следующая макрозадача 

Итак:

  1. Выполним весь синхронный код:

28a75f07f8359cf7344711ac3fc1b586.png

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

a7da2d06a8f0cc1eb4d77cceda00162a.png

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

1427868a217968f561a4c431f26d62b0.png

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

4. Выполним все вновь добавленные микрозадачи

8d733ed82f0946319a9b0470c986a6f9.png

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

18afad1a9058f249af3994a592ef345f.png

Результат

Вывод будет таким.

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');;

Заключение

Для всех подобных вопросов нужно просто запомнить три правила:

  1. Интерпретатор JavaScript всегда сначала выполняет синхронный код, а затем асинхронный.

  2. Микрозадачи имеют приоритет над макрозадачами.

7eb6121dd459340ddfbabb08f588eca7.png

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

a0ac8ff8b23c7422cb2042d8d41c2b1c.png

Завтра состоится открытый урок »Флексы и гриды — в чем отличие? ». На вебинаре на практике рассмотрим два популярные способа расположения элементов макета. Рассмотрим их плюсы и минусы, разные примеры применения. Регистрация по ссылке.

© Habrahabr.ru