Как стать автором
Обновить
1174.57
OTUS
Цифровые навыки от ведущих экспертов

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

Время на прочтение7 мин
Количество просмотров84K
Автор оригинала: bytefish

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. Найти код микрозадачи.

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

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

Выведется 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)

Анализ

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

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

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

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

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

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

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

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

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

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

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

Так:

Или вот так:

Таким образом, в приведенном выше коде коллбэк-функция 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');

Анализ

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

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

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

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

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

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

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

Итак:

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

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

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

 

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

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

Заключение

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

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

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

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


В марте в рамках специализации FullstackDeveloper пройдут два открытых урока, на которые приглашаем всех желающих.

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

Тема 2: Какими задачами проверяют ваше знание JavaScript? Посмотрим на типы задач, которые могут дать прямо во время интервью. Обсудим, как и что проверяют эти задачи, и что можно сделать, чтобы подготовиться к таким моментам. Записаться

Теги:
Хабы:
Всего голосов 53: ↑53 и ↓0+53
Комментарии25

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS