Я думаю, что могу проиллюстрировать это довольно красиво. Так как 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, и люди начинают зависеть от существующих интерфейсов.