Event Loop, Message & Job Queues, timers, process.nextTick() - Max-Starling/Notes GitHub Wiki
Цикл событий
JavaScript однопоточен. Это означает, что в один момент может выполняться только
одна вещь.
Это упрощает процесс разработки, поскольку нет необходимости думать о потоках,
их синхронизации, распределением ресурсов между ними и так далее.
Тем не менее, появляются некоторые ограничения.
Например, очень важно не блокировать основной поток трудоёмкими задачами.
В основном, в большинстве браузеров для каждой вкладки предназначен свой event
loop. Это нужно для того, чтобы сделать процессы независимыми друг от друга:
ведь одна страница не должна блокировать все остальные.
Среда управляет несколькими параллельными циклами событий.
Например, Web Worker'ы запускаются в своём собственном цикле событий.
Почти все операции ввода-вывода в JS неблокирующие: сетевые запросы, операции
с файлами с помощью Node.js и другое. Но чтобы этого достичь, необходимо
использовать колбэки, промиссы, async/await.
Стек вызовов (call stack) - это LIFO (list in - first out) очередь.
Цикл обработки событий непрерывно проверяет стек вызовов, чтобы увидеть, есть
ли какая-либо функция, которая должна быть запущена.
Message Queue
Когда вызывается setTimeout(), браузер или Node.js запускают таймер. Когда
таймер истекает, callback помещается в Message Queue.
В Message Queue также помещаются ответы fetch, события DOM и тд.
Цикл событий наделяет call stack приоритетом. Сперва выполняется всё, что там
есть, только затем рассматривается Message Queue.
JS не ждёт, пока выполняются запросы по типу fetch или setTimeout(), поскольку
они реализованы браузером и выполняются в других потоках.
ES6 Job Queue
В ECMAScript2015 ввели Job Queue.
Эта очередь используется промиссами.
Она имеет больший приоритет, чем Message Queue, поэтому выполняется раньше.
В Job Queue помещаются вся цепочка промиссов, поэтому они выполняются
последовательно.
Job'ы, которые разрешаются до завершения текущей функции, будут выполняться
сразу после текущей функции (когда стек вызовов очистился), а Message'ы будут
ждать, пока отработают все фунцкии.
process.nextTick()
Каждый раз, когда event loop совершает полный цикл, мы называем это тик(tick).
Когда мы передаём callback в nextTick(), мы посылаем движку команду
вызвать эту функцию в конце текущей операции, перед следующим тиком цикла
событий.
process.nextTick(() => { /*...*/ });
Пусть цикл событий занят выполнением текущей функции. После завершения операции
джижок запустит все функции, переданные в nextTick() во время операции.
Это способ выполнить функцию асинхронно сразу же после текущей, но так быстро,
как это возможно (не помещая её в очередь).
Вызов setTimeout(() => {}, 0) выполнится в следующем тике, намного позже, чем
если бы мы использовали nextTick().
Нужно использовать nextTick(), когда необходимо, чтобы к моменту следующей
итерации цикла событий код был уже выполнен.
setImmediate()
Когда нужно выполнить код асинхронно, но так быстро, как это это возможно,
можно использовать функцию setImmediate(), предоставленную Node.js.
setImmediate(() => { /*...*/ });
Любой callback, переданный в setImmediate(), будет выполнен на следующем тике (итерации) цикла событий.
process.nextTick() vs setImmediate() vs setTimeout(cb, 0)
Переданный в nextTick() сallback будет выполнен в текущем тике цикла событий
после того, как текущая операция закончится.
Поэтому nextTick() выполнится всегда перед setTimeout() и setImmediate(),
которые выполняются в следующем тике.
Порядок выполнения setTimeout(cb, 0) и setImmediate(cb) зависит от многих
факторов, но они будут выполнены в следующем тике. Можно сказать, что они
эквивалентны.
https://medium.freecodecamp.org/the-definitive-node-js-handbook-6912378afc6e