Учитель Руан задал вопрос в своем Твиттере:
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 гомологичны.
Пример
Простое текстовое выражение действительно немного суховато. В этом разделе используется пример, чтобы понять его шаг за шагом:
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');
Во-первых, цикл событий начинается с очереди задач макроса.В это время в очереди задач макроса есть только одна задача сценария (общий код), при обнаружении источника задачи задача будет сначала распределена по соответствующей задаче. иди в очередь. Таким образом, первый шаг приведенного выше примера выполняется, как показано на следующем рисунке:
а потом встретилconsole
заявление, прямой выводscript start
. После вывода задача скрипта продолжает выполняться вниз, встречаяsetTimeout
, как источник задач макроса, сначала распределит свои задачи в соответствующую очередь:
Задача скрипта продолжает выполняться вниз, встречаяPromise
пример. Первым параметром конструктора Promise являетсяnew
При выполнении, когда конструктор выполняется, параметры внутри попадают в стек выполнения для выполнения; и последующий.then
будет распределен на микрозадачуPromise
иди в очередь. Таким образом, он будет выведен первымpromise1
, затем выполнитеresolve
,Будуthen1
поставлен в соответствующую очередь.
Конструктор продолжает выполняться и встречаетsetTimeout
, а затем назначьте соответствующую задачу соответствующей очереди:
Задача сценария продолжает выполняться, и в конце выводится только одно предложение.script end
, в этот момент глобальная задача выполнена.
Согласно вышеизложенному, после каждого выполнения макрозадачи он будет проверять, есть ли микрозадачи, если да, то выполнять микрозадачи до тех пор, пока очередь микрозадач не опустеет.
Поэтому после выполнения задачи скрипта она начинает искать и очищать очередь микрозадач. На данный момент в микрозадаче толькоPromise
задача в очередиthen1
, поэтому просто выполните его напрямую и выведите результат выполненияthen1
. когда всеmicrotast
После того, как выполнение завершено, это означает, что первый раунд цикла завершен.
Это время, чтобы начать второй раунд цикла. Второй раунд цикла все еще из задачи макросаmacrotask
Начинать. На данный момент есть две задачи макроса:timeout1
а такжеtimeout2
.
выигратьtimeout1
выполнить, вывестиtimeout1
.此时微任务队列中已经没有可执行的任务了,直接开始第三轮循环:
Третий раунд цикла по-прежнему начинается с очереди задач макросов. На данный момент существует только одна задача макроса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);
Поток этого кода примерно следующий:
- Задача скрипта запускается первой. первая встреча
Promise
Например, конструктор выполняется первым, поэтому первым выводится число 4. На этом этапе задачи микрозадачиt2
а такжеt1
- Задача сценария продолжает выполняться, выводя 3. На этом выполнение первой задачи макроса завершено.
- Выполняйте все микрозадачи, снимайте их одну за другой
t2
а такжеt1
, которые выводят 2 и 1 соответственно - Код выполняется
Таким образом, вывод приведенного выше кода: 4321
Зачемt2
Будет ли он выполнен первым? Причины следующие:
- согласно сОбещания/спецификация A+:
На практике убедитесь, что методы onFulfilled и onRejected выполняются асинхронно и должны
then
Выполняется в новом стеке выполнения после цикла событий, в котором был вызван метод
-
Promise.resolve
Метод разрешено вызывать без параметров, и он напрямую возвращаетresolved
состояниеPromise
объект. немедленноresolved
изPromise
объекта в конце текущего "цикла событий", а не в начале следующего "цикла событий".
так,t2
Сравниватьt1
первым войдет в микрозадачуPromise
очередь.