Обещания против SetTimeout | ЯВАСКРИПТ

Это очень распространенный вопрос, который задают на собеседовании. Мне задали тот же вопрос в интервью, но я не смог на него ответить. Я настоятельно рекомендую посмотреть это видео на циклах событий, прежде чем двигаться дальше.

Циклы событий в Javascript

Javascript — это однопоточный язык программирования, что означает наличие только одного стека вызовов. Чтобы выполнить любую асинхронную задачу, вы должны остановить основной стек вызовов и дождаться, пока функция не будет извлечена из стека вызовов. Например, если вы хотите получить данные из API, вам нужно подождать, пока данные не будут получены, и весь DOM будет заблокирован до тех пор, пока не будут получены данные из API. Чтобы избавиться от такой проблемы, было введено понятие циклов событий. Веб-API вместе с задачами и очередью заданий расширяет возможности выполнения асинхронных задач без блокировки основного потока.

Обещания VS SetTimeout

Рассмотрим пример. Рассмотрим обещание и setTimeout, как показано ниже. Каким будет вывод следующего кода??

Promise.resolve(1).then(function resolve() {
console.log('Resolved!');
});
setTimeout(function timeout() {
console.log('Timed out!');
}, 0);

Подумайте, прежде чем переходить к ответу. Оба являются асинхронными, какой из них будет сначала перемещен в стек вызовов. Да, вы правильно угадали. Обещание будет разрешено первым, и результат будет.

Resolved!
Timed out!

Теперь подумайте об этом с другой стороны.

setTimeout(function timeout() {
console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
console.log('Resolved!');
});

Что теперь будет на выходе. Немного запутался? Вот где пригодится концепция очереди задач и очереди заданий. Дать ему шанс.

Вы будете удивлены, узнав, что результат остается прежним. Обещания быстрее, чем setTimeout.

Resolved!
Timed out!

Но в чем причина? Чтобы понять логику такого поведения, нам нужно еще немного углубиться в циклы событий и веб-API.

Очередь заданий и очередь задач

Давайте еще раз посмотрим на эксперимент с точки зрения цикла событий. Сделаю пошаговый разбор выполнения кода.

1. Стек вызовов выполняет setTimeout(..., 0) и планирует таймер. Обратный вызов timeout() хранится в веб-API:

setTimeout(function timeout() {
console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
console.log('Resolved!');
});

2. Стек вызовов выполняет Promise.resolve(true).then(resolve) и планирует разрешение промиса. Обратный вызов resolved() хранится в веб-API:

3. Обещание выполняется немедленно, а таймер немедленно истекает. Таким образом, обратный вызов таймера timeout() ставится в очередь в очередь задач, обратный вызов обещания resolve() ставится в очередь в очередь заданий:

4. Теперь самое интересное: цикл обработки событий отдает приоритет задачам, удаляющим очередь, а не задачам. Цикл обработки событий удаляет обратный вызов обещания resolve() из очереди заданий и помещает его в стек вызовов. Затем стек вызовов выполняет обратный вызов обещания resolve():

'Resolved!' зарегистрирован в консоли.

5. Наконец, цикл событий удаляет обратный вызов таймера timeout() из очереди задач в стек вызовов. Затем стек вызовов выполняет обратный вызов таймера timeout():

setTimeout(function timeout() {
console.log('Timed out!');
}, 0);
Promise.resolve(1).then(function resolve() {
console.log('Resolved!');
});

'Resolved!' зарегистрирован в консоли.

Стек вызовов пуст. Выполнение скрипта завершено.

Сводка

Почему немедленно выполненное обещание обрабатывается быстрее, чем таймер немедленного действия?

Из-за событийного цикла priorities задачи из очереди задач (в которой хранятся обратные вызовы выполненных обещаний) удаляются из очереди задач (в которой хранятся setTimeout() обратных вызовов с истекшим временем ожидания).