Уведомление
В версии Node 11 цикл событий Node стал таким же, как в браузере.
задний план
Цикл событий также является распространенной темой в js. В конце февраля, после прочтения «Подробного объяснения таймеров узла» учителя Жуана Ифэна, я обнаружил, что не могу полностью протестировать механизм выполнения циклов событий js, который я видел раньше.Я также проверил некоторые другие материалы, сделал заметки, неуместным, и обобщил его в документ.
Цикл событий и механизм выполнения в браузере отличаются от таковых в узле, и их не следует путать.браузерEvent loopэто спецификация, определенная в HTML5, а в узлеlibuvреализация библиотеки. В то же время, когда я читал книгу «Введение в NodeJs», я обнаружил, что механизм узлов в то время был другим, поэтому часть этой статьи о узлах относится к версии, когда эта статья была опубликована. Настоятельно рекомендуется прочитать первые три статьи по ссылке.
среда браузера
js выполняется как один поток (без учета веб-воркеров), а весь код выполняется в стеке вызовов основного потока. Когда задача основного потока очищается, она будет опрашивать задачи в очереди задач.
очередь задач
Асинхронные задачи делятся на две категории: задачи (макрозадачи, также известные как макрозадачи) и микрозадачи (микрозадачи). Когда условия выполнения соблюдены, задача и микрозадача помещаются в соответствующие очереди для помещения в основной поток для выполнения.Мы называем эти две очереди Очередь задач (также называемая очередью макрозадач) и Очередь микрозадач.
- Задача: Код в сценарии, Settimeate, Setinterval, I / O, UI Render.
- микрозадача: обещание, Object.observe, MutationObserver.
Конкретный процесс
- Завершите выполнение задач в основном потоке выполнения.
- Выньте выполнение задачи из Очереди микрозадач, пока она не опустеет.
- Избавьтесь от очереди макрозадачОдинвыполнение задачи.
- Выньте выполнение задачи из Очереди микрозадач, пока она не опустеет.
- Повторите 3 и 4.
То есть синхронное завершение, одна макрозадача, все микрозадачи, одна макрозадача, все микрозадачи...
Уведомление
- На странице браузера можно считать, что в начальном потоке выполнения нет кода, а код в каждом теге скрипта является самостоятельной задачей, то есть будет выполняться микрозадача, созданная в предыдущем скрипте, а затем код синхронизации в следующем сценарии будет выполнено.
- Если микрозадача была добавлена, она продолжит выполнение микрозадачи и «застряла» в макрозадаче.
- В некоторых версиях браузеров порядок выполнения не соответствует вышеуказанному, возможно, он не соответствует стандарту или JS и HTML часть стандартного конфликта. Вы можете прочитать первую из справочных статей.
-
new Promise((resolve, reject) =>{console.log(‘同步’);resolve()}).then(() => {console.log('异步')})
,Прямо сейчасpromise
изthen
а такжеcatch
Это микрозадача, а не собственный внутренний код. - Отдельные API для конкретных браузеров не указаны.
Поддельный код
while (true) {
宏任务队列.shift()
微任务队列全部任务()
}
среда узла
js выполняется как один поток, и весь код выполняется в стеке вызовов основного потока. Когда задача основного потока очищается, она будет опрашивать задачи в очереди задач.
стадия цикла
события в узлекаждый раундцикл согласнозаказРазделено на 6 этапов, реализация от libuv:
- таймеры: выполнить обратные вызовы setTimeout и setInterval, соответствующие условиям.
- Обратные вызовы ввода-вывода: существуют ли функции обратного вызова для завершенных операций ввода-вывода, остаток опроса от предыдущего раунда.
- бездействие, подготовка: можно игнорировать
- poll: Ожидание событий ввода-вывода, которые еще не завершились, ожидание закончится из-за таймеров и тайм-аутов.
- check: выполнить обратный вызов setImmediate.
- обратные вызовы close: закрыть все дескрипторы закрытия, некоторые события onclose.
исполнительный механизм
несколько очередей
В дополнение к типам задач в приведенной выше фазе цикла у нас остаются микрозадачи, общие для браузеров и узлов и уникальные для узла.process.nextTick
, которые мы называем очередью микрозадач и очередью NextTick.
Мы также называем очереди выполнения нескольких этапов в цикле «Очередь таймеров», «Очередь ввода-вывода», «Очередь проверки» и «Очередь закрытия».
перед циклом
Перед входом в первый цикл будут выполнены следующие операции:
- Синхронизировать задачу
- сделать асинхронный запрос
- Запланируйте время, когда таймер вступит в силу
- воплощать в жизнь
process.nextTick()
начать цикл
Согласно 6 этапам нашего цикла, он выполняется последовательно, каждый раз все задачи на текущем этапе извлекаются и выполняются, очередь NextTick очищается, а очередь микрозадач очищается. Затем выполнить следующий этап, после выполнения всех 6 этапов войти в следующий цикл. который:
- Очистите очередь таймеров в текущем цикле, очистите очередь NextTick и очистите очередь микрозадач.
- Очистите очередь ввода-вывода в текущем цикле, очистите очередь NextTick и очистите очередь микрозадач.
- Очистите очередь проверки в текущем цикле, очистите очередь NextTick и очистите очередь микрозадач.
- Очистите очередь закрытия в текущем цикле, очистите очередь NextTick и очистите очередь микрозадач.
- Введите следующий цикл.
Как можно заметить,nextTick
соотношение приоритетовpromise
Дождитесь, пока микрозадача станет высокой.setTimeout
а такжеsetInterval
соотношение приоритетовsetImmediate
высоко.
Уведомление
- Если создается, когда выполняется фаза таймеров
setImmediate
Он будет выполнен на этапе проверки этого цикла циклов, если он создан на этапе таймеров.setTimeout
, так как таймеры были удалены, он войдет в следующий цикл, и то же самое верно для создания задачи таймеров на этапе проверки. -
setTimeout
соотношение приоритетовsetImmediate
высокая, но из-заsetTimeout(fn,0)
Реальная задержка не может быть ровно 0 секунд, может показаться, что первая созданнаяsetTimeout(fn,0)
чемsetImmediate
Выполняется после обратного вызова.
Поддельный код
while (true) {
loop.forEach((阶段) => {
阶段全部任务()
nextTick全部任务()
microTask全部任务()
})
loop = loop.next
}
тестовый код
function sleep(time) {
let startTime = new Date()
while (new Date() - startTime < time) {}
console.log('1s over')
}
setTimeout(() => {
console.log('setTimeout - 1')
setTimeout(() => {
console.log('setTimeout - 1 - 1')
sleep(1000)
})
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 1 - then')
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 1 - then - then')
})
})
sleep(1000)
})
setTimeout(() => {
console.log('setTimeout - 2')
setTimeout(() => {
console.log('setTimeout - 2 - 1')
sleep(1000)
})
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 2 - then')
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 2 - then - then')
})
})
sleep(1000)
})
- Вывод браузера:
setTimeout - 1 //1为单个task 1s over setTimeout - 1 - then setTimeout - 1 - then - then setTimeout - 2 //2为单个task 1s over setTimeout - 2 - then setTimeout - 2 - then - then setTimeout - 1 - 1 1s over setTimeout - 2 - 1 1s over
- вывод узла:
setTimeout - 1 1s over setTimeout - 2 //1、2为单阶段task 1s over setTimeout - 1 - then setTimeout - 2 - then setTimeout - 1 - then - then setTimeout - 2 - then - then setTimeout - 1 - 1 1s over setTimeout - 2 - 1 1s over
Также видно, что цикл обработки событий отличается в браузере и узле.
Поскольку выполнение новой версии узла такое же, как и в браузере, в качестве примера используется среда браузера, а выходное значение консоли используется для ссылки на функцию, в которой находится значение. следует
<!--执行完主执行线程中的任务。-->
<!--取出Microtask Queue中任务执行直到清空。-->
<!--取出Macrotask Queue中一个任务执行。-->
<!--取出Microtask Queue中任务执行直到清空。-->
<!--重复3和4。-->
以 IQ 代指微任务队列,AQ 代指宏任务队列
1. 执行完主线程中任务:主执行线程执行完毕,setTimeout-1、setTimeout-2 进入等待
2. 清空 IQ:此时 IQ 中无任务
2. 执行 AQ 中一个任务: setTimeout-1 到时间后进入 AQ 中,被执行,执行过程中 setTimeout-1-1 进入等待状态,setTimeout-1-then 直接进入 IQ 队列,由于 setTimeout-1 中有 1s 等待,此时 setTimeout-2 肯定已经进入 AQ,setTimeout-1-1 也随后进入 AQ,此时结束状态为 IQ: [setTimeout-1-then],AQ: [setTimeout-2, setTimeout-1-1]
3. 清空 IQ: 此时 IQ 中有 setTimeout-1-then,执行 setTimeout-1-then,执行过程中,setTimout-1-then-then 直接被加入 IQ,所以 IQ 没清空,所以继续执行 setTimout-1-then-then,IQ 被清空,此时结束状态为 IQ: [], AQ: [setTimeout-2, setTimeout-1-1]
4. 执行 AQ 中一个任务:即执行 setTimeout-2
5. 清空 IQ: 这一步与 3 相似,所以输出 setTimeout-2-then、setTimeout-2-then-then,IQ 清空,此时结束状态为 IQ: [], AQ: [setTimeout-1-1, setTimeout-2-1]
6. 执行 AQ 中一个任务:即 setTimeout-1-1
7. 清空 IQ: 本身就为空
8. 执行 AQ 中一个任务:即 setTimeout-2-1
Справочная статья
- Tasks, microtasks, queues and schedulesнастоятельно рекомендуется
- Не путайте nodejs и цикл событий в браузеренастоятельно рекомендуется
- Модуль событий в узленастоятельно рекомендуется
- Понимание цикла событий 1 (анализ)
- Сведения о таймере узла