Говоря о цикле событий JavaScript из темы

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

Учитель Руан задал вопрос в своем Твиттере:

new Promise(resolve => {
    resolve(1);
    Promise.resolve().then(() => console.log(2));
    console.log(4)
}).then(t => console.log(t));
console.log(3);

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

цикл событий

Как мы все знаем, главной особенностью языка JavaScript является то, что он является однопоточным, то есть одновременно может выполняться только одно действие. согласно сСпецификация HTML:

To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. There are two kinds of event loops: those for browsing contexts, and those for workers.

Чтобы координировать события, взаимодействие с пользователем, сценарии, рендеринг пользовательского интерфейса и сетевую обработку, а также предотвращать блокировку основного потока, было создано решение Event Loop. Цикл событий содержит две категории: одна основана наBrowsing Context, один основан наWorker. Работа обоих независима, т. е. каждый «Поточная среда», в которой работает JavaScript, имеет независимый цикл событий, и каждый веб-воркер также имеет независимый цикл событий.

Цикл событий, описанный в этой статье, основан на контексте просмотра.

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

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

Согласно спецификации, цикл обработки событий проходит черезочередь задачмеханизм координации. В цикле событий может быть одна или несколько очередей задач, и очередь задач представляет собой набор упорядоченных задач; каждая задача имеет источник задачи, который происходит из одного и того же источника задач, которые должны быть помещены в одну и ту же очередь задач. , а задачи из разных источников добавляются в разные очереди.

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

  • В этом тике выберите задачу (самую старую задачу), которая входит в очередь первой, и выполните ее, если она есть (один раз)
  • Проверяет наличие микрозадач и, если они есть, выполняется непрерывно, пока очередь микрозадач не опустеет.
  • обновить рендер
  • Основной поток повторяет вышеуказанные шаги

Тщательный обзор спецификации показывает, что асинхронные задачи можно разделить наtaskа такжеmicrotaskДва типа асинхронных задач, зарегистрированных разными API, будут по очереди входить в соответствующие очереди, а затем ждать, пока цикл обработки событий поместит их в стек выполнения для выполнения.

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

(макрос) задача в основном включает в себя: сценарий (общий код), setTimeout, setInterval, ввод-вывод, события взаимодействия с пользовательским интерфейсом, setImmediate (среда Node.js)

микрозадача в основном включает: Promise, MutaionObserver, process.nextTick (среда Node.js)

В Node сначала очищается следующая очередь тиков, то есть функция, зарегистрированная через process.nextTick, а затем очищается другая очередь, например Promise.

API-интерфейсы, такие как setTimeout/Promise, являются источником задач, а то, что входит в очередь задач, — это конкретная задача выполнения, которую они указывают. Задачи из разных источников задач будут попадать в разные очереди задач. Где setTimeout и setInterval гомологичны.

event loop

Пример

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

console.log('script start');

setTimeout(function() {
  console.log('timeout1');
}, 10);

new Promise(resolve => {
    console.log('promise1');
    resolve();
    setTimeout(() => console.log('timeout2'), 10);
}).then(function() {
    console.log('then1')
})

console.log('script end');

Во-первых, цикл событий начинается с очереди задач макроса.В это время в очереди задач макроса есть только одна задача сценария (общий код), при обнаружении источника задачи задача будет сначала распределена по соответствующей задаче. иди в очередь. Таким образом, первый шаг приведенного выше примера выполняется, как показано на следующем рисунке:

step1

а потом встретилconsoleзаявление, прямой выводscript start. После вывода задача скрипта продолжает выполняться вниз, встречаяsetTimeout, как источник задач макроса, сначала распределит свои задачи в соответствующую очередь:

step2

Задача скрипта продолжает выполняться вниз, встречаяPromiseпример. Первым параметром конструктора Promise являетсяnewПри выполнении, когда конструктор выполняется, параметры внутри попадают в стек выполнения для выполнения; и последующий.thenбудет распределен на микрозадачуPromiseиди в очередь. Таким образом, он будет выведен первымpromise1, затем выполнитеresolve,Будуthen1поставлен в соответствующую очередь.

Конструктор продолжает выполняться и встречаетsetTimeout, а затем назначьте соответствующую задачу соответствующей очереди:

step3

Задача сценария продолжает выполняться, и в конце выводится только одно предложение.script end, в этот момент глобальная задача выполнена.

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

Поэтому после выполнения задачи скрипта она начинает искать и очищать очередь микрозадач. На данный момент в микрозадаче толькоPromiseзадача в очередиthen1, поэтому просто выполните его напрямую и выведите результат выполненияthen1. когда всеmicrotastПосле того, как выполнение завершено, это означает, что первый раунд цикла завершен.

step4

Это время, чтобы начать второй раунд цикла. Второй раунд цикла все еще из задачи макросаmacrotaskНачинать. На данный момент есть две задачи макроса:timeout1а такжеtimeout2.

выигратьtimeout1выполнить, вывестиtimeout1.此时微任务队列中已经没有可执行的任务了,直接开始第三轮循环:

step5

Третий раунд цикла по-прежнему начинается с очереди задач макросов. На данный момент существует только одна задача макросаtimeout2, выньте прямой выход.

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

script start
promise1
script end
then1
timeout1
timeout2

Суммировать

Возвращаясь к первоначальному названию этой статьи:

new Promise(resolve => {
    resolve(1);
    
    Promise.resolve().then(() => {
    	// t2
    	console.log(2)
    });
    console.log(4)
}).then(t => {
	// t1
	console.log(t)
});
console.log(3);

Поток этого кода примерно следующий:

  1. Задача скрипта запускается первой. первая встречаPromiseНапример, конструктор выполняется первым, поэтому первым выводится число 4. На этом этапе задачи микрозадачиt2а такжеt1
  2. Задача сценария продолжает выполняться, выводя 3. На этом выполнение первой задачи макроса завершено.
  3. Выполняйте все микрозадачи, снимайте их одну за другойt2а такжеt1, которые выводят 2 и 1 соответственно
  4. Код выполняется

Таким образом, вывод приведенного выше кода: 4321

Зачемt2Будет ли он выполнен первым? Причины следующие:

На практике убедитесь, что методы onFulfilled и onRejected выполняются асинхронно и должныthenВыполняется в новом стеке выполнения после цикла событий, в котором был вызван метод

  • Promise.resolveМетод разрешено вызывать без параметров, и он напрямую возвращаетresolvedсостояниеPromiseобъект. немедленноresolvedизPromiseобъекта в конце текущего "цикла событий", а не в начале следующего "цикла событий".

Голодание 6. Жуань Ифэн.com/#docs/promi…

так,t2Сравниватьt1первым войдет в микрозадачуPromiseочередь.

Ссылки по теме