Как быстро начать работу с VUE3.0: узнайте,
Хотя JavaScript является однопоточным, цикл событий максимально использует ядро системы, позволяя Node.js выполнять неблокирующие операции ввода-вывода. Хотя большинство современных ядер являются многопоточными, они могут обрабатывать многопоточные задачи в коде. фон. Когда задача завершена, ядро сообщает Node.js, а затем в цикл для выполнения добавляется соответствующий обратный вызов. В этой статье эта тема будет представлена более подробно.
Когда Node.js начинает выполнение, цикл событий. сначала будет инициализирован, обработает предоставленный входной скрипт (или поместит его в REPL, который не описан в этом документе). Это выполнит асинхронный вызов API, запланирует таймер или вызовет методprocess.nextTick(), а затем. начать обработку цикла событий. На
следующем
рисунке показана последовательность выполнения цикла событий.
┌─>│ таймеры │ + + │ │ ожидающие обратные вызовы │ + + │ │ простаивать, готовиться │ │ └──────────────┬───────────┘ ┌───────────────┐ │ ┌─────────────┴────────────┐ │ входящие: │ │ │ опрос │<─────┤ связи, │ │ └─────────────┬─────────────┘ │ данные и т. д. │ + │ │ проверить │ + + └──┤ закрытие обратных вызовов │ └──────────────────────────┘Каждое
поле представляет этап цикла событий.
На каждом этапе выполняется обратный вызов очереди FIFO. , каждый этап выполняется по-своему. Вообще говоря, когда цикл событий входит в этап, он выполняет любые операции на текущем этапе и начинает выполнять обратные вызовы в очереди текущего этапа, пока очередь не будет полностью использована или выполнена. максимальные данные. Когда очередь исчерпана или достигает максимального размера, цикл событий переходит к следующему этапу.
ТаймерыВ каждом процессе цикла событий, Node.js проверяет, ожидает ли он асинхронного ввода-вывода и таймер, и если нет,
Таймер определяет критическую точку, в которой будет выполнен обратный вызов, а не желаемое время
.Таймер выполнит выполнение как можно скорее по истечении указанного времени, однако планирование операционной системы или другие обратные вызовы могут задержать выполнение.
Технически говоря, фаза опроса определяет, когда будет выполнен обратный вызов.
Например, вы устанавливаете таймер на выполнение через 100 мс, но ваш скрипт читает файл асинхронно и занимает 95 мс
const fs = require('fs') ; функция someAsyncOperation (обратный вызов) { // Предположим, что это занимает 95 мс. fs.readFile('/путь/к/файлу', обратный вызов); } const timeoutScheduled = Date.now(); setTimeout(() => { константная задержка = Date.now() - timeoutScheduled; console.log(`${delay}мс прошло с момента моего планирования`); }, 100); // выполняем someAsyncOperation, выполнение которой занимает 95 мс someAsyncOperation(() => { const startCallback = Date.now(); // делаем что-то, что займет 10 мс... while (Date.now() - startCallback <10) { // ничего не делать } });
Когда цикл событий переходит в фазу опроса, очередь пуста (fs.readFile() еще не завершена), поэтому он будет ждать оставшиеся миллисекунды, пока не будет достигнут самый быстрый порог таймера. После 95 мс fs. .readFile() завершил чтение файла, и для добавления к фазе опроса и завершения выполнения потребуется 10 мс. Когда обратный вызов завершен, в очереди нет обратных вызовов, которые нужно выполнить, и цикл событий возвращается к фазе таймеров. для выполнения обратного вызова таймера. В этом примере вы увидите, что таймер задерживается на 105 мс перед выполнением.
Чтобы предотвратить блокировку цикла событий на этапе опроса, в libuv (библиотека языка C, которая реализует цикл событий и все асинхронное поведение на платформе) также есть. Фаза опроса. Максимальное прекращение опроса большего количества событий,
На этом этапе выполняются обратные вызовы для определенных системных операций (например, типов ошибок TCP). Например, некоторые системы *nix ожидают сообщения об ошибке, если TCP-сокет получает ECONNREFUSED при попытке подключения. Это будет поставлено в очередь для выполнения во время фазы ожидания обратного вызова.
Фаза опроса имеет две основные функции
цикл событий входит в фазу опроса и нет таймера, происходят следующие две вещи.
как только очередь опроса станет пустой
.определит, истек ли срок действия таймера. Если да, цикл событий достигнет стадии таймеров и выполнит
на этом этапе. Позволяет людям выполнять обратные вызовы сразу после завершения фазы опроса. Если фаза опроса становится бездействующей и сценарий поставлен в очередь с помощью setImmediate(), цикл обработки событий может перейти к фазе проверки вместо ожидания.
setImmediate() на самом деле представляет собой специальный таймер, который работает на отдельной фазе цикла событий. Он использует API libuv для планирования выполнения обратных вызовов после завершения фазы опроса.
Обычно по мере выполнения кода цикл событий в конечном итоге достигает фазы опроса, где он ожидает входящие соединения, запросы и т. д. Однако если обратный вызов запланирован с помощью setImmediate() и фаза опроса становится простой, она завершится и продолжится фазой проверки вместо ожидания события опроса.
Если сокет или операция внезапно закрываются (например, socket.destroy()), событие закрытия будет отправлено на этот этап, в противном случае оно будет отправлено через процесс.nextTick() setImmediate
setImmediate() и setTimeout() аналогичны, но ведут себя по-разному в зависимости от того, когда они вызываются.
Порядок выполнения каждого обратного вызова зависит от порядок их вызова. В этой среде, если один и тот же модуль вызывается одновременно, время будет ограничено производительностью процесса (например, на это будут влиять другие приложения, работающие на этом компьютере)
. , если мы не запустим следующий скрипт в I/O, хотя на него влияет производительность процесса, невозможно определить порядок выполнения этих двух таймеров:
// timeout_vs_immediate.js setTimeout(() => { console.log('тайм-аут'); }, 0); setImmediate(() => { console.log('немедленно'); });
$узел timeout_vs_immediate.js тайм-аут немедленный $ узел timeout_vs_immediate.js немедленный timeout
Однако, если вы перейдете в цикл ввода-вывода, немедленный обратный вызов всегда будет выполняться первым
// timeout_vs_immediate.js const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('тайм-аут'); }, 0); setImmediate(() => { console.log('немедленно'); }); });
$узел timeout_vs_immediate.js немедленный тайм-аут $ узел timeout_vs_immediate.js немедленныйПреимущество
тайм-аута
setImmediate перед setTimeout заключается в том, что setImmediate всегда будет выполняться первым перед любым таймером ввода-вывода, независимо от того, сколько таймеров существует.
Хотя процесс.nextTick() является частью асинхронного API, вы могли заметить, что он не отображается на диаграмме. Это связано с тем, что процесс.nextTick() не является частью технологии цикла событий. выполняется текущая операция. После завершения nextTickQueue будет выполнен независимо от текущего этапа цикла событий. Здесь операции определяются как преобразования базового обработчика C/C++ и обрабатывают JavaScript, который необходимо выполнить. Согласно диаграмме, вы можете вызвать Process.nextTick() на любом этапе. Все обратные вызовы, переданные вprocess.nextTick(), будут выполнены до того, как цикл обработки событий продолжит выполнение. Это может привести к некоторым неприятным ситуациям, поскольку позволяет вам вызывать рекурсивно. .
Почему такая ситуация включена в Node.js? Поскольку философия дизайна Node.js заключается в том, что API всегда должен быть асинхронным, даже если в этом нет необходимости, взгляните на следующий фрагмент кода:
function apiCall(arg, callback) { if (typeof arg !== 'строка') вернуть процесс.nextTick( перезвонить, новый TypeError('аргумент должен быть строкой') ); }
Фрагмент выполняет проверку параметров и, если они неверны, передает ошибку обратному вызову. Недавно API был обновлен, чтобы разрешить передачу параметров в методprocess.nextTick(), что позволяет ему принимать любые параметры, переданные после обратного вызова, в качестве аргументов обратного вызова, поэтому вам не нужно вкладывать функции.
Мы передаем ошибку обратно пользователю, но только в том случае, если мы разрешаем выполнение остальной части пользовательского кода. Используяprocess.nextTick(), мы гарантируем, что apiCall() всегда запускает обратный вызов после остальной части пользовательского кода и до того, как продолжить цикл событий. Для этого стек вызовов JS разрешается развернуть, а затем немедленно выполняется предоставленный обратный вызов, что позволяет выполнять рекурсивные вызовы Process.nextTick() без попадания в RangeError: превышен максимальный размер стека вызовов, начиная с версии 8.