Позвольте вам полностью понять цикл событий

Node.js внешний интерфейс браузер Promise

предисловие

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

Итак, прочитав множество статей, я хочу написать такой блог, не используя официальное описание, объединив собственное понимание и пример кода, и изложив его максимально популярным языком. Я надеюсь, что вы сможете использовать эту статью, чтобы понять, что такое механизм цикла событий и в чем разница между браузером и циклом событий NodeJS. Если в тексте есть опечатка, пожалуйста, оставьте сообщение и обсудите это вместе.

(PS: когда речь заходит о Event Loop, обязательно будет упомянут Promise. Я реализовал простую библиотеку Promise на основе спецификации Promise A+.исходный кодВыложите на Github, можете использовать как референс, если нужно. Я тоже буду вести блог про Promise в будущем. Если будет полезно, ставьте звезду~)

текст

Что такое цикл событий

Цикл событий — это модель выполнения с разными реализациями в разных местах. Браузеры и NodeJS реализуют свои собственные циклы событий, основанные на разных технологиях.

  • Цикл событий браузера находится вспецификация html5четко определены в.
  • Цикл событий NodeJS реализован на основе libuv. Вы можете обратиться к Nodeофициальная документацияи либувофициальная документация.
  • libuv уже реализовал Event Loop, а спецификация HTML5 определяет только модель Event Loop в браузере, а конкретная реализация остается на усмотрение производителя браузера.

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

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

  • setTimeout
  • setInterval
  • setImmediate (только узел)
  • requestAnimationFrame (только для браузера)
  • I/O
  • Рендеринг пользовательского интерфейса (эксклюзивно для браузера)

Микро-очередь, микрозадача, также называемая заданиями.Обратные вызовы других асинхронных задач по очереди будут поступать в очередь микрозадач, ожидая последующих вызовов.Эти асинхронные задачи включают в себя:

  • process.nextTick (только узел)
  • Promise
  • Object.observe
  • MutationObserver

(Примечание: это только для браузеров и NodeJS)

Цикл событий браузера

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

browser-eventloop

Эта картинка полностью описывает цикл событий браузера.Позвольте мне объяснить конкретный процесс выполнения кода JavaScript:

  1. Выполнить глобальный код синхронизации Script, некоторые из которых являются синхронными операторами, а некоторые — асинхронными операторами (такими как setTimeout и т. д.);
  2. После выполнения глобального кода скрипта стек вызовов Stack будет очищен;
  3. Берем callback-задачу в начале очереди из очереди микрозадач и помещаем ее в стек вызовов Stack для выполнения, после выполнения длина очереди микрозадач уменьшается на 1;
  4. Продолжайте вынимать задачу из головы очереди, помещать ее в стек вызовов Stack для выполнения и так далее, пока все задачи в очереди микрозадач не будут выполнены.Обратите внимание, что если во время выполнения микрозадачи будет сгенерирована другая микрозадача, она будет добавлена ​​в конец очереди и будет вызываться и выполняться в этом цикле.;
  5. Все задачи в очереди микрозадач выполнены, на данный момент очередь микрозадач пуста, и стек вызовов также пуст;
  6. Вынести задачу из головы очереди в очередь макрозадач и поместить ее в Стек для выполнения;
  7. После завершения выполнения стек стека вызовов пуст;
  8. Повторите шаги 3-7;
  9. Повторите шаги 3-7;
  10. ......

Как видите, это цикл событий браузера Event Loop

Вот 3 ключевых момента:

  1. Макрозадача очереди макросов берет только одну задачу из очереди для выполнения за раз, а затем выполняет задачи в очереди микрозадач после выполнения;
  2. Все задачи в очереди микрозадач будут извлекаться и выполняться по очереди, пока очередь микрозадач не опустеет;
  3. На рисунке нет узла рендеринга UI, потому что это определяется самим браузером, но так долго, как выполняется рендеринг пользовательского интерфейса, его узел выполняется после выполнения всех микрозакосок, а до следующей макротассы, а затем UI рендер.

Хорошо, концептуальных вещей так много, приходите и посмотрите несколько примеров кода, проверьте, освоили ли вы:

console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
})

setTimeout(() => {
  console.log(6);
})

console.log(7);

Какой здесь будет результат? Используйте то, что вы узнали выше, и попробуйте сами.

// 正确答案
1
4
7
5
2
3
6

Вы поняли это правильно?

Проанализируем весь процесс:


  1. Выполнить глобальный код скрипта

Step 1

console.log(1)

Stack Queue: [console]

Macrotask Queue: []

Microtask Queue: []

распечатать результат:
1

Step 2

setTimeout(() => {
  // 这个回调函数叫做callback1,setTimeout属于macrotask,所以放到macrotask queue中
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

Stack Queue: [setTimeout]

Macrotask Queue: [callback1]

Microtask Queue: []

распечатать результат:
1

Step 3

new Promise((resolve, reject) => {
  // 注意,这里是同步执行的,如果不太清楚,可以去看一下我开头自己实现的promise啦~~
  console.log(4)
  resolve(5)
}).then((data) => {
  // 这个回调函数叫做callback2,promise属于microtask,所以放到microtask queue中
  console.log(data);
})

Stack Queue: [promise]

Macrotask Queue: [callback1]

Microtask Queue: [callback2]

распечатать результат:
1
4

Step 5

setTimeout(() => {
  // 这个回调函数叫做callback3,setTimeout属于macrotask,所以放到macrotask queue中
  console.log(6);
})

Stack Queue: [setTimeout]

Macrotask Queue: [callback1, callback3]

Microtask Queue: [callback2]

распечатать результат:
1
4

Step 6

console.log(7)

Stack Queue: [console]

Macrotask Queue: [callback1, callback3]

Microtask Queue: [callback2]

распечатать результат:
1
4
7


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

Step 7

console.log(data)       // 这里data是Promise的决议值5

Stack Queue: [callback2]

Macrotask Queue: [callback1, callback3]

Microtask Queue: []

распечатать результат:
1
4
7
5


  1. В очереди микрозадач находится только одна задача, после выполнения задача в голове очереди берется из очереди макрозадач на выполнение.

Step 8

console.log(2)

Stack Queue: [callback1]

Macrotask Queue: [callback3]

Microtask Queue: []

распечатать результат:
1
4
7
5
2

Однако при выполнении Callback1 я столкнулся с другим промисом: промис выполняется асинхронно в очереди микрозадач и регистрирует callback-функцию callback4.

Step 9

Promise.resolve().then(() => {
  // 这个回调函数叫做callback4,promise属于microtask,所以放到microtask queue中
  console.log(3)
});

Stack Queue: [promise]

Macrotask v: [callback3]

Microtask Queue: [callback4]

распечатать результат:
1
4
7
5
2


  1. Выньте задачу макроса после того, как макрозадача будет выполнена, а затем перейдите в очередь микрозадач, чтобы в свою очередь вынуть выполнение

Step 10

console.log(3)

Stack Queue: [callback4]

Macrotask Queue: [callback3]

Microtask Queue: []

распечатать результат:
1
4
7
5
2
3


  1. После того, как все очереди микрозадач выполнены, переходим в очередь макрозадач, чтобы взять первую задачу на выполнение

Step 11

console.log(6)

Stack Queue: [callback3]

Macrotask Queue: []

Microtask Queue: []

распечатать результат:
1
4
7
5
2
3
6


  1. После того, как все вышеперечисленное выполнено, очередь стека пуста, очередь макрозадач пуста, а микроочередь пуста.

Stack Queue: []

Macrotask Queue: []

Microtask Queue: []

Окончательный результат печати:
1
4
7
5
2
3
6

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

console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
  
  Promise.resolve().then(() => {
    console.log(6)
  }).then(() => {
    console.log(7)
    
    setTimeout(() => {
      console.log(8)
    }, 0);
  });
})

setTimeout(() => {
  console.log(9);
})

console.log(10);

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

// 正确答案
1
4
10
5
6
7
2
3
9
8

Я считаю, что все ответили правильно Ключ здесь был упомянут ранее:

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

Примечание: Конечно, если вы продолжаете генерировать микрозадачи в микрозадачах, другие макрозадачи не могут быть выполнены, но эта операция не безгранична.В качестве примера возьмем микрозадачу process.nextTick() в NodeJS, ее верхний предел составляет 1000 единиц, что мы поговорим позже.

Цикл событий браузера здесь Давайте посмотрим на Цикл событий в NodeJS, он сложнее и механизм другой.

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

libuv

Давайте сначала посмотрим на структурную схему libuv:

node-libuv

Макро-очередь и микро-очередь в NodeJS

В цикле событий NodeJS задачами обратного вызова, выполняющими очередь макросов, являются:6 этапов,Как показано ниже:

node-eventloop-6phase

Задачи, решаемые каждым этапом, следующие:

  • Фаза таймеров: на этом этапе выполняется обратный вызов, запланированный setTimeout и setInterval.
  • Этап обратного вызова ввода/вывода: Выполнение обратных вызовов, отличных от обратных вызовов события закрытия, обратных вызовов, установленных таймерами, и обратных вызовов, установленных setImmediate().
  • бездействующий, подготовка стадии: используется только внутри узла
  • фаза опроса: получение новых событий ввода/вывода, нода заблокируется здесь при правильных условиях
  • этап проверки: выполнить обратные вызовы, установленные setImmediate().
  • закрыть этап обратных вызовов: выполнить socket.on('close', ....) эти обратные вызовы

В NodeJS в основном 4 очереди макросов.

Как видно из введения выше, события обратного вызова в основном располагаются в 4 очередях макрозадач:

  1. Timers Queue
  2. IO Callbacks Queue
  3. Check Queue
  4. Close Callbacks Queue

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

В NodeJS в основном 2 микроочереди.:

  1. Next Tick Queue: поместите задачу обратного вызова process.nextTick (обратный вызов)
  2. Другая микро-очередь: поместите другие микрозадачи, такие как Promise и т. д.

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

Конкретное можно лучше понять с помощью следующего рисунка:

node-eventloop

В целом объясняя процесс цикла событий nodejs:

  1. Синхронный код, выполняющий глобальный скрипт
  2. Выполнить микрозадачу микрозадачи, сначала выполнить все задачи во всей очереди следующего тика, а затем выполнить все задачи в другой очереди микрозадач
  3. Начать выполнение макрозадачи макрозадачи, всего 6 этапов, начиная с первого этапа, чтобы выполнить все задачи в соответствующей макрозадаче каждого этапа, обратите внимание, что здесь находятся все задачи в очереди макрозадач всех этапов, в цикле событий браузера Он берет только первую задачу из очереди макросов и выполняет ее.После выполнения задачи макрозадачи каждого этапа начинает выполняться микрозадача, то есть шаг 2.
  4. Очередь таймеров -> Шаг 2 -> Очередь ввода-вывода -> Шаг 2 -> Очередь проверки -> Шаг 2 -> Закрыть очередь обратного вызова -> Шаг 2 -> Очередь таймеров ......
  5. Это цикл событий узла

Что касается очереди макрозадач и очереди микрозадач NodeJS, я нарисовал две картинки для справки:

node-microtaskqueue

node-macrotaskqueue

Что ж, концепция понятна, давайте попробуем на нескольких примерах:

первый пример

console.log('start');

setTimeout(() => {          // callback1
  console.log(111);
  setTimeout(() => {        // callback2
    console.log(222);
  }, 0);
  setImmediate(() => {      // callback3
    console.log(333);
  })
  process.nextTick(() => {  // callback4
    console.log(444);  
  })
}, 0);

setImmediate(() => {        // callback5
  console.log(555);
  process.nextTick(() => {  // callback6
    console.log(666);  
  })
})

setTimeout(() => {          // callback7              
  console.log(777);
  process.nextTick(() => {  // callback8
    console.log(888);   
  })
}, 0);

process.nextTick(() => {    // callback9
  console.log(999);  
})

console.log('end');


Обновление 2018.9.20

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

  • setTimeout(fn, 0) не является строго 0, обычно setTimeout(fn, 3) или что-то еще, будет определенное время задержки, когда setTimeout(fn, 0) и setImmediate(fn) появятся в одном и том же синхронном коде, будет быть две ситуации.

  • Дело 1: код синхронизации выполняется, а время таймера не истекло.Обратный вызов setImmediate сначала регистрируется в очереди проверки, затем выполняется микроочередь, а затем выполняется очередь макросов.Он начинается с очереди таймеров и обнаруживает, что там не является обратным вызовом. Спускайтесь вниз до тех пор, пока в очереди проверки не будет обратного вызова, выполните, а затем истечет время таймера (при условии, что эффект истечения остается тем же после выполнения очереди таймера), обратный вызов таймера зарегистрирован в очереди таймеров, а обратный вызов таймера может быть выполнен только после выполнения следующего раунда выполнения цикла в очереди таймеров;Таким образом, в этом случае заданный вызов Setimmediate (Fn) предшествует выполнение обратного вызова (FN, 0) обратного вызова.

  • Случай 2: код синхронизации еще не выполнен, таймер истекает первым, обратный вызов таймера сначала регистрируется в Очереди таймеров, а после выполнения setImmediate его обратный вызов регистрируется в Очереди проверки. Затем, после выполнения кода синхронизации, выполняется микроочередь, а затем сначала выполняется очередь таймеров, сначала выполняется обратный вызов таймера, затем выполняется проверка очереди и выполняется обратный вызов setImmediate;Итак, в этом случае обратный вызов setTimeout(fn, 0) выполняется перед обратным вызовом setImmediate(fn).

  • Следовательно, нет уверенности в одновременном вызове setTimeout(fn, 0) и setImmediate в синхронном коде, но если они помещены в обратный вызов ввода-вывода, такой как readFile('xx', function () {//... . }) обратный вызов, то обратный вызов ввода-вывода находится в очереди ввода-вывода, обратный вызов setTimeout истечения регистрируется в очереди таймеров, обратный вызов setImmediate регистрируется в очереди проверки, очередь ввода-вывода выполняется в очереди проверки, а очередь таймера получает следующий цикл, поэтому обратный вызов setImmediate таков. В этом случае он должен быть выполнен до обратного вызова setTimeout(fn, 0).

Подводя итог, этот пример не очень хорош. Если Settimeout (Fn, 0) и setimmediate (fn) хотят убедиться, что результат уникален, поместите его в обратный вызов IO. Приведенный выше код может выполнить все из них синхронно. Код все размещен в обратном вызове IO, и результат уникален.

конец обновления



Пожалуйста, используйте то, что вы узнали раньше, и тщательно проанализируйте это...

// 正确答案
start
end
999
111
777
444
888
555
333
666
222

Вы поняли это правильно? Давайте проанализируем это вместе:

  1. Выполнить глобальный код скрипта, сначала распечататьstart, Выполнить по нисходящей, зарегистрировать callback1 для setTimeout в очереди таймеров, а затем выполнить его по нисходящей, зарегистрировать callback5 для setImmediate в очереди проверки, а затем выполнить по нисходящей, зарегистрировать callback7 для setTimeout в очереди таймеров и перейти к следующему, зарегистрируйте обратный вызов9 процесса.nextTick в микроочередь Next Tick Queue и распечатайте последний шагend. На данный момент обратные вызовы каждой очереди следующие:

очередь макросов

Timers Queue: [callback1, callback7]

Check Queue: [callback5]

Очередь обратного вызова ввода-вывода: []

Close Callback Queue: []

микроочередь

Next Tick Queue: [callback9]

Other Microtask Queue: []

распечатать результат
start
end

  1. После выполнения глобального сценария все задачи обратного вызова в микрозадаче Next Tick Queue выполняются последовательно. На данный момент в Next Tick Queue есть только один callback9, возьмите его и поместите в стек вызовов для выполнения, и напечатайте999.

очередь макросов

Timers Queue: [callback1, callback7]

Check Queue: [callback5]

Очередь обратного вызова ввода-вывода: []

Close Callback Queue: []

микроочередь

Next Tick Queue: []

Other Microtask Queue: []

распечатать результат
start
end
999

  1. Начните последовательно выполнять все задачи в соответствующих очередях макросов на 6 этапах, сначала выполните все задачи в очереди таймеров на первом этапе, сначала выньте выполнение callback1 и напечатайте111, функция callback1 продолжает выполняться, по очереди помещает callback2 в очередь таймеров, callback3 в очередь проверки, callback4 в очередь Next Tick Queue, а затем выполняется callback1. Затем возьмите callback7, который занимает первое место в очереди таймеров, и выполните его, напечатайте777, поместите callback8 в Next Tick Queue, и выполнение будет завершено. На данный момент очереди следующие:

очередь макросов

Timers Queue: [callback2]

Check Queue: [callback5, callback3]

Очередь обратного вызова ввода-вывода: []

Close Callback Queue: []

микроочередь

Next Tick Queue: [callback4, callback8]

Other Microtask Queue: []

распечатать результат
start
end
999
111
777

  1. После выполнения очереди макрозадач каждого этапа из 6 этапов начнется выполнение микрозадачи, в это время все задачи в Next Tick Queue вынимаются и выполняются, callback4 начинает выполняться и печатает444, затем callback8 начинает выполняться, печатая888, После выполнения очереди следующего тика она начинает выполнять задачи из очереди других микрозадач.Поскольку она пуста, она продолжает работу вниз.

очередь макросов

Timers Queue: [callback2]

Check Queue: [callback5, callback3]

Очередь обратного вызова ввода-вывода: []

Close Callback Queue: []

микроочередь

Next Tick Queue: []

Other Microtask Queue: []

распечатать результат
start
end
999
111
777
444
888

  1. На втором этапе очередь обратного вызова ввода-вывода пуста, пропустите ее, третий и четвертый этапы обычно используются узлом внутри, пропустите и войдите в очередь проверки пятого этапа. Выньте выполнение callback5, распечатайте555, поместить callback6 в Next Tick Queue, выполнить callback3, распечатать333.

очередь макросов

Timers Queue: [callback2]

Check Queue: []

Очередь обратного вызова ввода-вывода: []

Close Callback Queue: []

микроочередь

Next Tick Queue: [callback6]

Other Microtask Queue: []

распечатать результат
start
end
999
111
777
444
888
555
333

  1. Выполните очередь микрозадач, сначала выполните Next Tick Queue, выньте выполнение обратного вызова6, распечатайте666, выполнение завершено, поскольку очередь других микрозадач пуста, пропустите ее.

очередь макросов

Timers Queue: [callback2]

Check Queue: []

Очередь обратного вызова ввода-вывода: []

Close Callback Queue: []

микроочередь

Next Tick Queue: [callback6]

Other Microtask Queue: []

распечатать результат
start
end
999
111
777
444
888
555
333

  1. Выполнить задачи 6-го этапа Close Callback Queue, пусто, пропустить, ну цикл в это время закончился. Войдите в следующий цикл, выполните все задачи в очереди таймеров на первом этапе, выньте callback2 для выполнения и распечатайте222,полный. В этот момент все очереди, включая очереди макрозадач и очереди микрозадач, пусты, и ничего не будет напечатано.

очередь макросов

Timers Queue: []

Check Queue: []

Очередь обратного вызова ввода-вывода: []

Close Callback Queue: []

микроочередь

Next Tick Queue: [callback6]

Other Microtask Queue: []

Окончательные результаты
start
end
999
111
777
444
888
555
333
666
222

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


Давайте представим Promise, чтобы увидеть пример:

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})

new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})
process.nextTick(function() {
  console.log('6');
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

Разберем его внимательно.По сравнению с предыдущим примером, в Other Microtask Queue также есть задачи обратного вызова из-за наличия Promises.При выполнении этапа микрозадачи сначала выполняются все задачи в Next Tick Queue, а затем Other Выполняется микрозадача Все задачи в Очереди, а затем переход к следующему этапу макрозадачи. Поймите это, я считаю, что каждый может проанализировать это и дать правильный ответ прямо ниже.Если у вас есть какие-либо вопросы, пожалуйста, оставьте сообщение и обсудите со мной.

// 正确答案
1
7
6
8
2
4
9
11
3
10
5
12

setTimeout против setImmediate

  • setTimeout(fn, 0) выполняется на этапе таймеров и выполняется на этапе опроса, чтобы определить, достигнуто ли указанное время таймера.
  • setImmediate(fn) выполняется на этапе проверки

Порядок выполнения двух может быть определен в соответствии с текущей средой выполнения:

  • Если оба вызываются в основном модуле, то порядок выполнения зависит от производительности процесса, и порядок случайный
  • Если ни один из них не вызывается в основном модуле, то есть в круге ввода-вывода, то обратный вызов setImmediate всегда будет выполняться первым, потому что фаза проверки будет первой.

setImmediate против process.nextTick

  • Задача обратного вызова setImmediate(fn) будет вставлена ​​в очередь макросов Check Queue
  • Задача обратного вызова process.nextTick(fn) будет вставлена ​​в микро-очередь Next Tick Queue
  • Глубина вызова process.nextTick(fn) ограничена, верхний предел — 1000, а setImmedaite — нет.

Суммировать

  1. Цикл событий браузера и цикл событий NodeJS различаются, и механизмы реализации также различаются, поэтому не путайте их.
  2. NodeJS можно понимать как имеющий 4 очереди макрозадач и 2 очереди микрозадач, но при выполнении макрозадач есть 6 этапов. Сначала выполните глобальный код сценария, а после очистки стека вызовов синхронного кода сначала выньте все задачи из очереди следующего тика очереди микрозадач и поместите их в стек вызовов для выполнения, а затем выньте все задачи. задачи по очереди из очереди других микрозадач в стек вызовов для выполнения.Затем запустите этапы задачи макроса 6. На каждом этапе все задачи в очереди задач макроса вынимаются и выполняются (обратите внимание, что это отличается от браузера, браузер принимает только одну), и каждый этап задачи макроса После этого запускаем выполнение микрозадач, а затем начинаем выполнять следующий этап макрозадач, формируя таким образом цикл событий.
  3. NodeJS можно понимать как имеющий 4 очереди макрозадач и 2 очереди микрозадач, но при выполнении макрозадач есть 6 этапов. Сначала выполните глобальный код сценария, а после очистки стека вызовов синхронного кода сначала выньте все задачи из очереди следующего тика очереди микрозадач и поместите их в стек вызовов для выполнения, а затем выньте все задачи. задачи по очереди из очереди других микрозадач в стек вызовов для выполнения. Затем запустите 6 этапов задачи макроса, каждый этап берет все задачи в очереди задач макроса для выполнения (обратите внимание, что это отличается от браузера, браузер берет только один), после выполнения 6 этапов, затем запустите выполнение микрозадач для формирования цикла событий.
  4. MacroTask включает в себя: setTimeout, setInterval, setImmediate (узел), requestAnimation (браузер), ввод-вывод, рендеринг пользовательского интерфейса.
  5. Микрозадача включает в себя: process.nextTick(Node), Promise, Object.observe, MutationObserver.

Поправка 3: В новой версии Node после выполнения каждой макрозадачи будет выполняться микрозадача, что соответствует модели браузера.

Добро пожаловать, чтобы обратить внимание на мой общедоступный номер

微信公众号

Ссылка на ссылку

Не путайте nodejs и цикл событий в браузере

Модуль событий в узле

Promises, process.nextTick And setImmediate

Различные циклы событий браузера и узла

Tasks, microtasks, queues and schedules

Понимание анализа цикла событий