setImmediate против nextTick

Node.js версия 0.10 была выпущена сегодня и представлена setImmediate, Документация по изменениям API предлагает использовать его при выполнении рекурсии nextTick звонки.

Из того, что MDN говорит, кажется, очень похоже на process.nextTick,

Когда я должен использовать nextTick и когда я должен использовать setImmediate?

9 ответов

Решение

Использование setImmediate если вы хотите поставить функцию в очередь за любыми обратными вызовами событий ввода / вывода, которые уже находятся в очереди событий. использование process.nextTick эффективно поставить функцию в начало очереди событий, чтобы она выполнялась сразу после завершения текущей функции.

Таким образом, в случае, когда вы пытаетесь разорвать долгосрочную работу с привязкой к процессору с помощью рекурсии, вы теперь захотите использовать setImmediate скорее, чем process.nextTick ставить в очередь следующую итерацию, так как в противном случае любые обратные вызовы событий ввода / вывода не получат возможность работать между итерациями.

Как иллюстрация

import fs from 'fs';
import http from 'http';

const options = {
  host: 'www.stackru.com',
  port: 80,
  path: '/index.html'
};

describe('deferredExecution', () => {
  it('deferredExecution', (done) => {
    console.log('Start');
    setTimeout(() => console.log('TO1'), 0);
    setImmediate(() => console.log('IM1'));
    process.nextTick(() => console.log('NT1'));
    setImmediate(() => console.log('IM2'));
    process.nextTick(() => console.log('NT2'));
    http.get(options, () => console.log('IO1'));
    fs.readdir(process.cwd(), () => console.log('IO2'));
    setImmediate(() => console.log('IM3'));
    process.nextTick(() => console.log('NT3'));
    setImmediate(() => console.log('IM4'));
    fs.readdir(process.cwd(), () => console.log('IO3'));
    console.log('Done');
    setTimeout(done, 1500);
  });
});

даст следующий вывод

Start
Done
NT1
NT2
NT3
TO1
IO2
IO3
IM1
IM2
IM3
IM4
IO1

Я надеюсь, что это может помочь понять разницу.

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

В комментариях к ответу явно не говорится, что nextTick сместился с макросемантики на микросемантику.

до узла 0.9 (когда был введен setImmediate), nextTick работал в начале следующего стека вызовов.

начиная с узла 0.9, nextTick работает в конце существующего стека вызовов, тогда как setImmediate находится в начале следующего стека вызовов

проверьте https://github.com/YuzuJS/setImmediate для инструментов и деталей

Несколько отличных ответов, подробно описывающих, как они оба работают.

Просто добавив тот, который отвечает на конкретный вопрос:

Когда я должен использовать nextTick и когда я должен использовать setImmediate?


Всегда используйте setImmediate,


Цикл событий Node.js, таймеры и process.nextTick() Документ включает в себя следующее:

Мы рекомендуем разработчикам использовать setImmediate() во всех случаях, потому что это легче рассуждать (и это приводит к коду, который совместим с более широким разнообразием сред, таких как браузер JS.)


Ранее в документе он предупреждает, что process.nextTick может привести к...

некоторые плохие ситуации, потому что это позволяет вам "голодать" ваш ввод / вывод, делая рекурсивный process.nextTick() вызовы, которые не позволяют циклу событий достичь фазы опроса.

Как выясняется, process.nextTick может даже голодать Promises:

Promise.resolve().then(() => { console.log('this happens LAST'); });

process.nextTick(() => {
  console.log('all of these...');
  process.nextTick(() => {
    console.log('...happen before...');
    process.nextTick(() => {
      console.log('...the Promise ever...');
      process.nextTick(() => {
        console.log('...has a chance to resolve');
      })
    })
  })
})

С другой стороны, setImmediate " легче рассуждать " и позволяет избежать таких проблем:

Promise.resolve().then(() => { console.log('this happens FIRST'); });

setImmediate(() => {
  console.log('this happens LAST');
})

Так что, если нет особой необходимости в уникальном поведении process.nextTick Рекомендуемый подход заключается в " использовании setImmediate() во всех случаях ".

Проще говоря, process.NextTick() будет выполняться при следующем тике цикла событий. Однако setImmediate, в основном, имеет отдельную фазу, которая гарантирует, что обратный вызов, зарегистрированный в setImmediate(), будет вызываться только после фазы обратного вызова IO и опроса.

Пожалуйста, обратитесь к этой ссылке для хорошего объяснения: https://medium.com/the-node-js-collection/what-you-should-know-to-really-understand-the-node-js-event-loop-and-its-metrics-c4907b19da4c

Упрощенные события цикла событий

Я рекомендую вам проверить раздел документации, посвященный Loop, чтобы лучше понять. Некоторый фрагмент, взятый оттуда:

У нас есть два вызова, которые похожи на пользователей, но их имена сбивают с толку.

  • process.nextTick () запускается сразу на той же фазе

  • setImmediate() запускается на следующей итерации или "галочке"
    цикл событий

По сути, имена должны поменяться местами. process.nextTick() срабатывает быстрее, чем setImmediate(), но это артефакт прошлого, который вряд ли изменится.

Никогда не следует прерывать работу с интенсивным использованием ЦП, используя

Никогда не следует прерывать такую ​​работу, используя process.nextTick (). Это приведет к тому, что очередь микрозадач никогда не опустеет - ваше приложение навсегда останется в одной и той же фазе!-

Каждая фаза цикла событий содержит несколько обратных вызовов:

После срабатывания он всегда будет оставаться в той же фазе.

Пример

      const nt_recursive = () => process.nextTick(nt_recursive);
nt_recursive(); // setInterval will never run

const si_recursive = () => setImmediate(si_recursive);
si_recursive(); // setInterval will run

setInterval(() => console.log('hi'), 10);

В этом примере setInterval() представляет некоторую асинхронную работу, которую выполняет приложение, например, отвечая на входящие HTTP-запросы.

Однажды nt_recursive() функция запущена, приложение заканчивается очередью микрозадач, которая никогда не опорожняется, и асинхронная работа никогда не обрабатывается.

Но альтернативная версия si_recursive() не имеет такого же побочного эффекта.

Выполнение вызовов в фазе проверки добавляет обратные вызовы в очередь фазы проверки следующей итерации цикла событий, а не в очередь текущей фазы.

Случай может использовать process.nextTick

Сделайте так, чтобы функция обрабатывала все асинхронно.

      function foo(count, callback) {
  if (count <= 0) {
    return process.nextTick(() => callback(new TypeError('count > 0')));
  }
  myAsyncOperation(count, callback);
}

- Томас Хантер. Распределенные системы с Node.jsТомас Хантер. Распределенные системы с Node.js

В этом случае либо используя setImmediate() или process.nextTick()хорошо; просто убедитесь, что вы случайно не введете рекурсию.

С , ваша функция будет вызывать свой обратный вызов асинхронно. Обратные вызовы, отложенные с помощью, называются микрозадачами (process.nextTick, Promises, queueMicrotask, MutationObserver) и выполняются сразу после завершения текущей операции, даже до того, как будет запущено любое другое событие ввода-вывода.

При использовании setImmediate() выполнение ставится в очередь в фазе цикла событий, которая наступает после обработки всех событий ввода-вывода. С process.nextTick()запускается перед любым уже запланированным вводом-выводом, он будет выполняться быстрее, но при определенных обстоятельствах он также может задержать выполнение любого обратного вызова ввода-вывода на неопределенный срок (также известный как голодание ввода-вывода), например, при наличии рекурсивный вызов. Голодание происходит, если обратный вызов задерживается на неопределенное время. Это никогда не может случиться с .

С использованием setTimeout(callback, 0)имеет поведение, сравнимое с поведением , обратные вызовы, запланированные с помощью setImmediate()выполняются быстрее, чем запланированные с setTimeout(callback,0).

Другие вопросы по тегам