Об авторе: nekron Ant Financial Data Experience Technology Team
последовательность
я всегдаEvent LoopКогнитивные определения программы — это все оценки, которые могут быть известны или нет, поэтому я сохраняю только поверхностные понятия и никогда не изучаю их, пока не прочитаю эту статью——«На этот раз досконально изучите механизм выполнения JavaScript». Автор этой статьи очень дружелюбен, и я извлек много пользы из самого маленького примера, но последний пример включает в себяchrome
а такжеNode
Результаты операции ниже очень разные, мне очень любопытно, и я чувствую, что необходимо изучить этот кусок знания.
По вышеперечисленным причинам родилась эта статья, изначально я планировал разделить полный текст на три части: спецификация, реализация и применение. Но, к сожалению, из-за своих ограниченных знаний я не могуEvent LoopКогда мы представляем сценарий приложения с характеристиками приложения, на самом деле нет вывода, в результате чего для приложения слишком мало места, поэтому это не отражено в заголовке.
(Все среды выполнения кода в этой статье включают только Node v8.9.4 и Chrome v63)
ЧАСТЬ 1: Спецификация
Зачем нужен цикл событий?
Поскольку Javascript изначально разрабатывался как однопоточный язык, чтобы добиться неблокировки основного потока,Event LoopТакой план появился.
викторина (1)
Давайте сначала посмотрим на кусок кода, каким будет результат печати?
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
Promise.resolve().then(() => {
console.log(3)
}).then(() => {
console.log(4)
})
console.log(5)
Незнакомый с циклом событий, я попытался проанализировать следующее:
- Прежде всего, давайте сначала исключим асинхронный код, сначала узнаем код, выполняемый синхронно, мы можем знать, что первая печать должна быть
1、5
- Однако имеют ли setTimeout и Promise приоритет? Или зависит от порядка выполнения?
- Кроме того, будет ли setTimeout вставлен между многоуровневым и Promise?
В замешательстве я попытался запустить код, и правильный результат:1、5、3、4、2
.
Так почему же?
определение
Кажется, нам нужно начать с определения спецификации, поэтому проверьте его.Спецификация HTML, спецификация действительно подробная (луо) и подробная (суо), поэтому выкладывать не буду.Ключевые шаги по доработке таковы:
- Выполнить самую старую задачу (один раз)
- Проверьте, есть ли микрозадача, затем продолжайте выполнение, пока очередь не будет очищена (несколько раз)
- выполнить рендеринг
Молодец, я еще не разобрался с проблемой, а тут вдруг еще 2 понятияtaskа такжеmicrotask, что меня еще больше смутило. . .
Не паникуйте, внимательно прочитайте документ и знайте, что эти два понятия относятся к классификации асинхронных задач.Асинхронные задачи, зарегистрированные разными API, будут по очереди входить в соответствующие им очереди, а затем ждать их.Event LoopВставьте их в стек выполнения по очереди для выполнения.
В задачу в основном входит:setTimeout
,setInterval
,setImmediate
,I/O
,UI交互事件
Микрозадача в основном включает в себя:Promise
,process.nextTick
,MutaionObserver
весь основнойEvent Loopкак показано на рисунке:
- Очередь можно рассматривать как структуру данных для хранения функций, которые необходимо выполнить.
- Функция, зарегистрированная API типа таймера (setTimeout/setInterval), войдет в очередь задач по истечении срока ее действия (механизм работы таймера здесь подробно описываться не будет)
- Остальные функции регистрации API напрямую входят в соответствующие им очереди задач/микрозадач.
- Цикл событий выполняется один раз, извлекает задачу из очереди задач для выполнения
- Цикл событий продолжает проверять, пуста ли очередь микрозадач, и выполняется последовательно до тех пор, пока очередь не опустеет.
Продолжить тест (2)
В это время оглянитесь на предыдущую测试(1)
, я обнаружил, что концепция была очень ясной, я сразу получил правильный ответ, я чувствовал себя мило и больше не боялсяEvent Loopсейчас~
Затем приготовьтесь ответить на более сложные вопросы (этот вопрос взят изпоследовательностьСтатья, упомянутая в, я удалила первойprocess.nextTick
):
console.log(1)
setTimeout(() => {
console.log(2)
new Promise(resolve => {
console.log(4)
resolve()
}).then(() => {
console.log(5)
})
})
new Promise(resolve => {
console.log(7)
resolve()
}).then(() => {
console.log(8)
})
setTimeout(() => {
console.log(9)
new Promise(resolve => {
console.log(11)
resolve()
}).then(() => {
console.log(12)
})
})
проанализируйте, как показано ниже:
- Код, который выполняется синхронно, сначала выводит:
1、7
- Затем очистите очередь микрозадач:
8
- Первая задача выполняет:
2、4
- Затем очистите очередь микрозадач:
5
- Вторая задача выполняет:
9、11
- Затем очистите очередь микрозадач:
12
существуетchrome
Давай запустим, хорошо!
Уверен, я надут, готов добавитьprocess.nextTick
Затем продолжите тестирование на node. Сначала тестирую первую задачу, код такой:
console.log(1)
setTimeout(() => {
console.log(2)
new Promise(resolve => {
console.log(4)
resolve()
}).then(() => {
console.log(5)
})
process.nextTick(() => {
console.log(3)
})
})
new Promise(resolve => {
console.log(7)
resolve()
}).then(() => {
console.log(8)
})
process.nextTick(() => {
console.log(6)
})
С предыдущим накоплением я уверенно написал ответ на этот раз:1、7、8、6、2、4、5、3
.
Однако, красавчик всего на 3 секунды, правильный ответ:1、7、6、8、2、4、3、5
.
Я запутался, но быстро понял, что значит**process.nextTick
Зарегистрированная функция имеет более высокий приоритет, чемPromise
**, так что все логично~
Далее я тестирую вторую задачу:
console.log(1)
setTimeout(() => {
console.log(2)
new Promise(resolve => {
console.log(4)
resolve()
}).then(() => {
console.log(5)
})
process.nextTick(() => {
console.log(3)
})
})
new Promise(resolve => {
console.log(7)
resolve()
}).then(() => {
console.log(8)
})
process.nextTick(() => {
console.log(6)
})
setTimeout(() => {
console.log(9)
process.nextTick(() => {
console.log(10)
})
new Promise(resolve => {
console.log(11)
resolve()
}).then(() => {
console.log(12)
})
})
От кусочка пирога растет мудрость, на этот раз я освоил приоритет микрозадачи, поэтому ответ должен быть таким:
- Вывод первой задачи:
1、7、6、8、2、4、3、5
- Затем вторая задача выводит:
9、11、10、12
Впрочем, плевок в лицо. . .
В первый раз, когда я выполняю его, вывод:1、7、6、8、2、4、9、11、3、10、5、12
(То есть выполнение двух задач смешивается вместе). Я продолжаю, и иногда он печатает ответ, который я ожидал.
Реальность так необъяснима! какие! какие!
(Ах, простите, кровь не могла остановиться какое-то время)Итак, почему это? ? ?
ЧАСТЬ 2: Реализация
Как говорится:
Спецификации составляются людьми, код пишется людьми. --анонимный
Спецификации не могут охватывать все сценарии, хотяchrome
а такжеnode
Оба основаны на движке v8, но движок отвечает только за управление стеком памяти, а API разрабатывается и реализуется каждой средой выполнения.
Викторина (3)
Таймер - это всеEvent LoopОчень важная часть процесса, давайте начнем с таймера, чтобы почувствовать разницу между спецификациями и реализациями.
Во-первых, давайте проведем небольшой тест: что он выдаст?
setTimeout(() => {
console.log(2)
}, 2)
setTimeout(() => {
console.log(1)
}, 1)
setTimeout(() => {
console.log(0)
}, 0)
Студенты, у которых не было глубокого контакта с таймером, ответят непосредственно из настройки задержки в коде:0、1、2
.
И некоторые другие студенты с некоторым опытом могут ответить:2、1、0
. потому чтоДокументация MDN для setTimeoutВ спецификации HTML указано, что минимальная задержка составляет 4 мс:
(Дополнительное примечание: минимальная задержка устанавливается, чтобы оставить время отдыха ЦП)
In fact, 4ms is specified by the HTML5 spec and is consistent across browsers released in 2010 and onward. Prior to (Firefox 5.0 / Thunderbird 5.0 / SeaMonkey 2.2), the minimum timeout value for nested timeouts was 10 ms.
И студенты, которые действительно пострадали, скажут вам, что ответ таков:1、0、2
. и, будь тоchrome
ещеnode
Следующие результаты совпадают.
(Исправление ошибки: после многих проверок порядок вывода в узле все еще не гарантирован, а таймер узла на самом деле является метафизикой~)
таймер в Хроме
от测试(3)
Из результатов видно, что эффект задержки 0 мс и 1 мс одинаков, в чем причина? Давайте сначала проверимblink
реализация.
(Я не знаю, как искать, где размещен код Blink. К счастью, имя файла относительно очевидно. Ответ не занял много времени)
(Я сразу выложил нижний код, если вас интересует верхний код, проверьте сами)
// https://chromium.googlesource.com/chromium/blink/+/master/Source/core/frame/DOMTimer.cpp#93
double intervalMilliseconds = std::max(oneMillisecond, interval * oneMillisecond);
Здесь интервал - это входящее значение.Можно видеть, что результат входящего 0 и входящего 1 составляет одну миллисекунду, что составляет 1 мс.
Это объясняет, почему поведение 1 мс и 0 мс одинаково, и что происходит с 4 мс? я снова подтвердилСпецификация HTML, обнаружил, что хотя и есть ограничение в 4 мс, но есть условия, подробности см. в пункте 11 спецификации:
If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4.
И что интересно,Документация MDN на английском языкеОписание также соответствует этой спецификации.
Рискну предположить, что спецификация HTML5 изначально имела минимальную спецификацию 4 мс, но она была пересмотрена в последующих редакциях, и я не думаю, что даже исключено, что спецификация согласуется с реализацией, то есть негативное влияние.
таймер в узле
Этоnode
Почему эффект задержки 0 мс и 1 мс одинаков?
(Или управляемый код github выглядит удобным, просто найдите целевой код напрямую)
// https://github.com/nodejs/node/blob/v8.9.4/lib/timers.js#L456
if (!(after >= 1 && after <= TIMEOUT_MAX))
after = 1; // schedule on next tick, follows browser behavior
В комментариях в коде прямо указано, что минимальное время 1 мс соответствует поведению браузера.
Цикл событий в узле
Таймер выше — это небольшой эпизод, теперь мы возвращаемся к сути этой статьи —Event Loop.
давайте сосредоточимся наnode
по реализацииblink
Реализация этой статьи не расширена, в основном потому, что:
-
chrome
В настоящее время поведение соответствует норме - Доступно не так много документации
- Я не умею искать и даже не знаю, где найти код ядра. . .
(Пропустить весь процесс исследования...)
Глядя непосредственно на вывод, следующий рисунокnode
изEvent Loopвыполнить:
Дополнительные инструкции:
-
Node
изEvent Loopпоэтапно, этапы последовательно- expired timers and intervals, то есть setTimeout/setInterval, срок действия которого истекает
- I/O events, включая файлы, сети и т. д.
- immediates, функция, зарегистрированная через setImmediate
- close handlers, обратный вызов близкого события, такое как отключение соединения TCP
- Очередь микрозадач будет очищаться после задач синхронизации и каждого этапа.
- Приоритет очищенnext tick queue, то есть через
process.nextTick
зарегистрированная функция - снова пустоother queue, обычный как Обещание
- Приоритет очищенnext tick queue, то есть через
- Отличие от спецификации в том, что нода будет очищать очередь на текущем этапе, то есть выполнять все задачи
Повторный тест (2)
Понять реализацию, оглянуться назад测试(2)
:
// 代码简略表示
// 1
setTimeout(() => {
// ...
})
// 2
setTimeout(() => {
// ...
})
Видно, что из-за двухsetTimeout
та же задержка, слитая в ту жеexpired timers queue, выполняя вместе. Так что ставь второйsetTimeout
Если задержка изменена на более чем 2 мс (1 мс недействительна, подробности см. выше), вы можете гарантировать, что эти дваsetTimeout
Он не истечет одновременно, а также может обеспечить согласованность результатов вывода.
тогда, если я поставлю один изsetTimeout
изменить наsetImmediate
, можно ли также гарантировать порядок вывода?
ответне можем. Хотя можно гарантироватьsetTimeout
а такжеsetImmediate
Обратные вызовы не будут выполняться в беспорядке, но нет гарантии, чтоsetTimeout
а такжеsetImmediate
Порядок, в котором выполняются обратные вызовы.
существуетnode
Далее, глядя на простейший пример, вывод следующего кода не гарантируется:
setTimeout(() => {
console.log(0)
})
setImmediate(() => {
console.log(1)
})
// or
setImmediate(() => {
console.log(0)
})
setTimeout(() => {
console.log(1)
})
Суть дела в томsetTimeout
Когда он истекает, только просроченныйsetTimeout
чтобы убедиться, что вsetImmediate
выполнял раньше.
Но если так例子(2)
, хотя согласованность вывода в принципе может быть гарантирована, но настоятельно не рекомендуется:
// 先使用setTimeout注册
setTimeout(() => {
// ...
})
// 一系列micro tasks执行,保证setTimeout顺利到期
new Promise(resolve => {
// ...
})
process.nextTick(() => {
// ...
})
// 再使用setImmediate注册,“几乎”确保后执行
setImmediate(() => {
// ...
})
Или другой способ мышления для обеспечения порядка:
const fs = require('fs')
fs.readFile('/path/to/file', () => {
setTimeout(() => {
console.log('timeout')
})
setImmediate(() => {
console.log('immediate')
})
})
Итак, почему такой код гарантируетsetImmediate
Обратный вызов имеет приоритет надsetTimeout
Как насчет выполнения обратного вызова?
Потому что, когда два обратных вызова успешно зарегистрированы одновременно, текущийnode
изEvent LoopОн находится вI/O queueэтап, а следующий этапimmediates queue, поэтому можно гарантировать, что даже еслиsetTimeout
истек и также будетsetImmediate
Выполняется после обратного вызова.
ЧАСТЬ 3: Применение
потому что я только учусьEvent Loop, независимо от того, основано ли оно на спецификациях или реализациях, сценариев приложений, которые я могу придумать, относительно немного. этот мастерEvent Loop, где мы можем его использовать?
Проверить на ошибки
Обычно мы не сталкиваемся с очень сложными сценариями очереди. Однако в случае возникновения проблемы, например, когда невозможно гарантировать порядок выполнения, мы можем быстро определить проблему.
опрос
Когда будут сложные сценарии очереди? Например, на собеседовании нет гарантии, что будет такой странный тест, чтобы с ним легко справились~
приоритет выполнения
Серьезно, если вы посмотрите на спецификации, микрозадачи имеют приоритет над выполнением задач. Если есть логика, которую необходимо выполнить в первую очередь, очередь микрозадач будет выполнена раньше, чем задача.Эту функцию можно использовать для разработки механизма планирования задач в фреймворке.
если изnode
С точки зрения реализации, при правильном выборе времени выполнение микрозадачи может даже блокировать ввод-вывод, что является палкой о двух концах.
Таким образом, высокоприоритетный код может использоватьPromise
/process.nextTick
Регистрация выполняется.
эффективность
отnode
С точки зрения реализации,setTimeout
этот тип таймераAPI, вам нужно создавать объекты таймера и повторять операции, а обработка задач должна выполняться в небольшой корневой куче, а временная сложность составляетO(log(n)). Напротив,process.nextTick
а такжеsetImmediate
Временная сложностьO(1), более высокая эффективность.
Если есть требование к эффективности выполнения, используйте его в первую очередь.process.nextTick
а такжеsetImmediate
.
разное
Добро пожаловать всем, чтобы добавить вместе~
Ссылаться на
- На этот раз досконально изучите механизм выполнения JavaScript.
- Tasks, microtasks, queues and schedules
- Event Loop and the Big Picture
- Timers, Immediates and Process.nextTick
- What you should know to really understand the Node.js Event Loop
- Узел асинхронный эти вещи
- libuv design
Студенты, которым интересна команда, могут подписаться на колонку или отправить свое резюме по адресу tao.qit####alibaba-inc.com.replace('####', '@'). Добро пожаловать, присоединяйтесь~
Оригинальный адрес:GitHub.com/proto team/no…