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