setImmediate против nextTick


336

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

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

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


20
В блоге есть 5 параграфов об этом изменении blog.nodejs.org/2013/03/11/node-v0-10-0-stable
mak

1
С точки зрения производительности это выглядит nextTickбыстрее, чем setImmediateпри больших вычислениях.

10
Для справки, я сначала прочитал эти пять абзацев и все же остановился на этом вопросе, когда он мне ничего не прояснил. Принятый ответ является намного более кратким и фактически описывает то, что setImmediateделает более подробно.
Chev

Я объяснил разницу очень подробно в моем блоге .
plafer

Это тот случай, когда GC может работать раньше setImmediate, но не раньше nextTick?

Ответы:


511

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

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


86
Обратные вызовы, передаваемые в process.nextTick, обычно вызываются в конце текущего потока выполнения и, таким образом, выполняются примерно так же быстро, как синхронный вызов функции. Если этот флажок не установлен, это приведет к истощению цикла событий, предотвращая возникновение ввода-вывода. setImmediates ставятся в очередь в созданном порядке и удаляются из очереди один раз за итерацию цикла. Это отличается от process.nextTick, который будет выполнять callback-вызовы process.maxTickDepth из очереди за одну итерацию. setImmediate будет возвращаться к циклу событий после запуска обратного вызова из очереди, чтобы убедиться, что ввод / вывод не истощен.
Бенджамин Грюнбаум

2
@UstamanSangat setImmediate поддерживается IE10 + только, все остальные браузеры упорно отказываются реализовать стандарт вероятного будущего , потому что им не нравится , избивают Microsoft. Чтобы добиться аналогичного результата в FF / Chrome, вы можете использовать postMessage (опубликовать сообщение в собственном окне). Вы также можете рассмотреть возможность использования requestAnimationFrame, особенно если ваши обновления связаны с пользовательским интерфейсом. setTimeout (func, 0) вообще не работает как process.nextTick.
Fabspro

45
@fabspro "потому что им не нравится, когда меня избивают в Microsoft", заставляет вас о чем-то болеть. Это главным образом потому, что это ужасно, ужасно названо. Если есть одна функция setImmediate времени, никогда, никогда не будет запущена, это немедленно. Название функции - полная противоположность тому, что она делает. nextTick и setImmediate было бы лучше переключаться; setImmediate выполняется сразу после завершения текущего стека (до ожидания ввода-вывода), а nextTick выполняется в конце следующего тика (после ожидания ввода-вывода). Но это уже было сказано тысячу раз.
Крейг Эндрюс

4
@fabspro Но, к сожалению, эта функция называется nextTick. nextTick выполняется «немедленно», а setImmediate больше похож на setTimeout / postMessage.
Роберт

1
@CraigAndrews Я бы избежал, requestAnimationFrameпотому что это не всегда происходит (я определенно видел это, я думаю, что пример был вкладкой, а не текущей вкладкой), и его можно вызвать до того, как страница завершит рисование (то есть браузер все еще занят рисованием).
Робокат

68

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

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

const options = {
  host: 'www.stackoverflow.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

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

Обновлено:

process.nextTick()Обратные вызовы, отложенные с помощью run, до запуска любого другого события ввода-вывода, в то время как с setImmediate () выполнение ставится в очередь за любым событием ввода-вывода, которое уже находится в очереди.

Node.js Design Patterns , Марио Кашаро (вероятно, лучшая книга о node.js / js)


2
Это действительно полезно, спасибо. Я думаю, что изображения и примеры - самый быстрый способ что-то понять.
Джон Джеймс

1
Я думаю, что важно указать, что setTimeout () и setImmediate (), когда не в цикле ввода-вывода, порядок является недетерминированным в зависимости от производительности процесса. nodejs.org/en/docs/guides/event-loop-timers-and-nexttick For example, if we run the following script which is not within an I/O cycle (i.e. the main module), the order in which the two timers are executed is non-deterministic, as it is bound by the performance of the process: Итак, этот ответ на самом деле не отвечает точной разнице, а является лишь примером, который может варьироваться в разных контекстах
Actung

Как указано @Actung. очень важно знать, находятся ли setTimetout и setImmediate в цикле ввода-вывода или нет, чтобы определить результат.
Раджика Ималь

50

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


2
Довольно четкое описание. Я думаю, что этот ответ нуждается в большем количестве голосов.
Actung

Хорошо объяснено (Y)
Дхирадж Шарма

важно повторить предупреждение Node об использовании process.nextTick. Если вы поместите в очередь большое количество обратных вызовов в nextTickQueue, вы потенциально можете заморозить цикл обработки событий, гарантируя, что фаза опроса никогда не будет достигнута. Это причина, почему вы должны предпочитать setImmediate.
Faridcs

1
Спасибо, это было лучшее объяснение. пример кода действительно помог.
Skyhavoc

@skyhavoc рад, что смог помочь!
Чев

30

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

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

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

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


11

Проще говоря, 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-метрика-c4907b19da4c

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


8

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

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

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


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


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

Мы рекомендуем разработчикам использовать 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()во всех случаях ».


1

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

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

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.