Я думаю, що можу це досить добре проілюструвати. Оскільки 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 дозріває, і люди залежать від існуючих інтерфейсів.