Анализ setTimeout и Promise

Node.js внешний интерфейс JavaScript Promise

Об асинхронном программировании JavaScript,ПреамбулаПроанализирована модель параллелизма JavaScript, основанная на цикле событий. Просто случайно ответил на другой на StackoverflowО проблемах, связанных с порядком выполнения setTimeout и Promise, так что обобщите эти знания, поделитесь ими с другими читателями и улучшите серию статей об асинхронном программировании в JavaScript.

мой личный блог

предисловие

Давайте сначала рассмотрим распространенные вопросы на фронтенд-интервью:

var p1 = new Promise(function(resolve, reject){
    resolve(1);
})
setTimeout(function(){
  console.log("will be executed at the top of the next Event Loop");
},0)
p1.then(function(value){
  console.log("p1 fulfilled");
})
setTimeout(function(){
  console.log("will be executed at the bottom of the next Event Loop");
},0)

В каком порядке выполняется и выводится код в приведенном выше примере? Этот вопрос также является источником создания этой статьи, и ответ таков:

p1 fulfilled
will be executed at the top of the next Event Loop
will be executed at the bottom of the next Event Loop

Затем объясните причины вывода результатов.Прочитав эту статью, вы сможете понять разницу между setTimeout и Promise.

цикл событий

Подробности о цикле событий см.Асинхронное программирование JavaScriptВ этой статье уже были введены, и эта статья не будет их повторять, а сделает некоторые дополнения и обобщения:

事件循环

исполняемый код

Подумайте об этом, как выполняется код JavaScript? Он выполняется построчно? Конечно, нет, движок JavaScript анализирует и выполняет код JavaScript по частям, а не построчно. При анализе и выполнении блоков кода будет выполняться предварительная работа, такая как продвижение переменной/функции и определение переменной/функции. Упомянутые здесь блоки кода обычно называютсяИсполняемый код, обычно включающий глобальный код, код функции и код выполнения eval.. Проделанная предварительная работа заключается в создании контекста выполнения (контекста выполнения).

стек контекста выполнения

Всякий раз, когда движок JavaScript начинает выполнение приложения, он создает стек контекста выполнения (последним пришел — первым обслужен) для управления контекстом выполнения. При выполнении фрагмента исполняемого кода контекст выполнения создается, затем помещается в стек, а после выполнения контекст извлекается из стека.

function funA() {
    console.log('funA')
}

function funB() {
    fun3A();
}

function funC() {
    funB();
}

funC();
ECStack.push(<funC> functionContext);

// funC中调用funB,需创建funB执行上下文,入栈
ECStack.push(<funB> functionContext);

// funB内调用funA,入栈上下文
ECStack.push(<funA> functionContext);

// funA执行完毕,退栈
ECStack.pop();

// funB执行完毕,退栈
ECStack.pop();

// funC执行完毕,退栈
ECStack.pop();

// javascript继续执行后续代码

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

асинхронная задача

Изучая модель параллелизма цикла событий JavaScript, мы узналиsetTimeoutиPromiseВсе асинхронные задачи вызываются, что у них общего, то есть все они управляются/планируются через очередь задач. Так в чем же между ними разница? Продолжить ниже.

очередь задач

Основное содержание и механизм очереди задач были представлены в предыдущей статье, вы можете просмотреть ее.Эта статья расширяет введение в очередь задач. JavaScript управляет всеми асинхронными задачами через очереди задач, а очереди задач также можно разделить на очередь MacroTask и очередь MicoTask.

MacroTask Queue

MacroTask Queue (очередь задач макросов) в основном включаетsetTimeout, setInterval, setImmediate, requestAnimationFrame, UI rendeing, `ввод-вывод в NodeJS и т. д.

MicroTask Queue

MicroTask Queue в основном включает две категории:

  1. Независимый обратный вызов microTask: например, Promise, его функции обратного вызова успеха/неудачи не зависят друг от друга;
  2. Микрозадача составного обратного вызова: например,Object.observe, MutationObserverи в NodeJsprocess.nextTick, различные обратные вызовы состояния находятся в одном и том же теле функции;

Макрозадача и микрозадача

JavaScript делит асинхронные задачи на MacroTask и MicroTask, так в чем же между ними разница?

  1. Выполнять синхронный код последовательно, пока выполнение не будет завершено;
  2. Проверить очередь MacroTask, если есть сработавшая асинхронная задача, взять первую и вызвать ее обработчик событий, затем перейти к третьему шагу, если асинхронной задачи для обработки нет, то сразу перейти к третьему шагу;
  3. Проверьте очередь MicroTask, затем выполните все запущенные асинхронные задачи, выполните функции обработки событий последовательно, пока выполнение не будет завершено, а затем перейдите ко второму шагу.Если асинхронных задач для обработки нет, вернитесь ко второму шагу и последовательно выполнить последующие шаги;
  4. Наконец, вернитесь ко второму шагу, продолжайте проверять очередь MacroTask и последовательно выполняйте последующие шаги;
  5. Таким образом, если все асинхронные задачи будут обработаны, она завершится;

task Queue

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

Следует отметить, что здесь говорится每个任务结束时Задача в обычно относится к макроЗадаче, есть специальная задача -выполнение скрипта (JavaScript Run), который также является макрозадачей, которая немедленно преобразует скрипт JavaScript вJavaScript RunЗадача помещается в очередь macroTask.

обзор

Введение содержания этой статьи в основном закончено, так каков же порядок вывода первой темы в предыдущей статье? Кратко объясните:

  1. Начните выполнение скрипта JavaScript, который будетJavaScript RunВставьте в очередь macroTask;
  2. После синхронизации resolvePromise;
  3. Поместите первую задачу setTimeout в очередь macroTask
  4. Поместите задачу Proimse.then в очередь микрозадач;
  5. Поместите вторую задачу setTimeout в стек и войдите в очередь macroTask;
  6. После завершения синхронного выполнения кода выходим из первой макрозадачи, т.е.JavaScript Run;
  7. Выполнить четкую микрозадачу;
  8. Выполнить следующую макрозадачу;

Наконец, давайте еще раз просмотрим содержание с темой:

setTimeout(function(){
  console.log("will be executed at the top of the next Event Loop")
},0)
var p1 = new Promise(function(resolve, reject){
    setTimeout(() => { resolve(1); }, 0);
});
setTimeout(function(){
    console.log("will be executed at the bottom of the next Event Loop")
},0)
for (var i = 0; i < 100; i++) {
    (function(j){
        p1.then(function(value){
           console.log("promise then - " + j)
        });
    })(i)
}

Каков результат кода? Просто проверьте это:

will be executed at the top of the next Event Loop
promise then - 0
promise then - 1
promise then - 2
...
promise then - 99
will be executed at the bottom of the next Event Loop
  1. Во-первых, весь код выполняется синхронно, в ходе которого регистрируются три асинхронных задачи setTimeout и 100 асинхронных задач Promise;
  2. Затем проверьте очередь MacroTask, возьмите первую MacroTask с истекшим сроком действия и выполните выходные данные.will be executed at the top of the next Event Loop;
  3. Затем проверьте очередь микрозадач и обнаружите, что просроченных микрозадач нет, и перейдите к шагу 4;
  4. Снова проверьте MacroTask, выполните второй обработчик setTimeout, разрешите Promise;
  5. Затем проверьте очередь MicroTask и обнаружите, что Promise был разрешен, и его функции асинхронной обработки могут быть выполнены, выполнены последовательно и выведены.promise then - 0кpromise then - 99;
  6. Наконец, снова проверьте очередь MacroTask, выполните выводwill be executed at the bottom of the next Event Loop
  7. Поочередно проверяйте две очереди асинхронных задач туда и обратно, пока выполнение не будет завершено;