Я думаю, что могу проиллюстрировать это довольно красиво. Так как nextTick
вызывается в конце текущей операции, рекурсивный вызов может привести к блокировке продолжения цикла обработки событий. setImmediate
решает эту проблему, запуская фазу проверки цикла событий, позволяя циклу событий нормально продолжаться.
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
источник: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
Обратите внимание, что фаза проверки начинается сразу после фазы опроса. Это связано с тем, что фаза опроса и обратные вызовы ввода / вывода являются наиболее вероятными местами для выполнения ваших вызовов setImmediate
. Таким образом, в идеале большинство из этих вызовов на самом деле будут довольно немедленными, просто не такими немедленными, как те, nextTick
которые проверяются после каждой операции и технически существуют вне цикла обработки событий.
Давайте рассмотрим небольшой пример различия между setImmediate
и process.nextTick
:
function step(iteration) {
if (iteration === 10) return;
setImmediate(() => {
console.log(`setImmediate iteration: ${iteration}`);
step(iteration + 1); // Recursive call from setImmediate handler.
});
process.nextTick(() => {
console.log(`nextTick iteration: ${iteration}`);
});
}
step(0);
Допустим, мы только что запустили эту программу и проходили первую итерацию цикла событий. Он вызовет step
функцию с нулевой итерацией. Затем он зарегистрирует два обработчика, один для setImmediate
и один для process.nextTick
. Затем мы рекурсивно вызываем эту функцию из setImmediate
обработчика, который будет запущен на следующем этапе проверки. nextTick
Обработчик будет работать в конце текущей операции прерывания цикла обработки событий, так что даже если он был зарегистрирован вторым будет реально работать первым.
Порядок заканчивается следующим образом: nextTick
запускается по окончании текущей операции, начинается цикл следующего события, выполняются обычные фазы цикла события, setImmediate
запускается и рекурсивно вызывает нашу step
функцию, чтобы начать процесс заново. Текущая операция заканчивается, nextTick
пожары и т. Д.
Вывод приведенного выше кода будет:
nextTick iteration: 0
setImmediate iteration: 0
nextTick iteration: 1
setImmediate iteration: 1
nextTick iteration: 2
setImmediate iteration: 2
nextTick iteration: 3
setImmediate iteration: 3
nextTick iteration: 4
setImmediate iteration: 4
nextTick iteration: 5
setImmediate iteration: 5
nextTick iteration: 6
setImmediate iteration: 6
nextTick iteration: 7
setImmediate iteration: 7
nextTick iteration: 8
setImmediate iteration: 8
nextTick iteration: 9
setImmediate iteration: 9
Теперь давайте переместим наш рекурсивный вызов step
в наш nextTick
обработчик вместо setImmediate
.
function step(iteration) {
if (iteration === 10) return;
setImmediate(() => {
console.log(`setImmediate iteration: ${iteration}`);
});
process.nextTick(() => {
console.log(`nextTick iteration: ${iteration}`);
step(iteration + 1); // Recursive call from nextTick handler.
});
}
step(0);
Теперь, когда мы переместили рекурсивный вызов step
в nextTick
обработчик, все будет вести себя в другом порядке. Наша первая итерация цикла событий выполняется и вызывает step
регистрацию как setImmedaite
обработчика, так и nextTick
обработчика. После завершения текущей операции наш nextTick
обработчик запускает, который рекурсивно вызывает step
и регистрирует другой setImmediate
обработчик, а также другой nextTick
обработчик. Поскольку nextTick
обработчик срабатывает после текущей операции, регистрация nextTick
обработчика в nextTick
обработчике приведет к тому, что второй обработчик будет запущен сразу после завершения текущей операции обработчика. Эти nextTick
обработчики будут продолжать стрелять, предотвращая текущий цикл событий из когда - либо продолжения. Мы пройдем через все нашиnextTick
обработчики, прежде чем мы видим один setImmediate
обработчик огня.
Вывод приведенного выше кода в конечном итоге будет:
nextTick iteration: 0
nextTick iteration: 1
nextTick iteration: 2
nextTick iteration: 3
nextTick iteration: 4
nextTick iteration: 5
nextTick iteration: 6
nextTick iteration: 7
nextTick iteration: 8
nextTick iteration: 9
setImmediate iteration: 0
setImmediate iteration: 1
setImmediate iteration: 2
setImmediate iteration: 3
setImmediate iteration: 4
setImmediate iteration: 5
setImmediate iteration: 6
setImmediate iteration: 7
setImmediate iteration: 8
setImmediate iteration: 9
Обратите внимание, что если бы мы не прерывали рекурсивный вызов и не прерывали его после 10 итераций, то nextTick
вызовы продолжали бы рекурсивно и никогда не позволяли бы циклу обработки событий переходить к следующему этапу. Это nextTick
может стать блокировкой при рекурсивном использовании, тогда как setImmediate
срабатывает в следующем цикле событий, а установка другого setImmediate
обработчика изнутри одного вообще не прерывает текущий цикл событий, позволяя ему продолжать выполнение фаз цикла событий как обычно.
Надеюсь, это поможет!
PS - Я согласен с другими комментаторами в том, что имена двух функций можно легко поменять местами, поскольку nextTick
звучит так, как будто они будут срабатывать в следующем цикле событий, а не в конце текущего, а конец текущего цикла более «немедленный» «чем начало следующего цикла. О, хорошо, это то, что мы получаем по мере развития API, и люди начинают зависеть от существующих интерфейсов.