Обещания против 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()
обратных вызовов с истекшим временем ожидания).