簡體   English   中英

setImmediate 與 nextTick

[英]setImmediate vs. nextTick

Node.js 版本 0.10 今天發布並引入了setImmediate API 更改文檔建議在執行遞歸nextTick調用時使用它。

MDN 的說法來看,它似乎與process.nextTick非常相似。

什么時候應該使用nextTick什么時候應該使用setImmediate

如果您想在事件隊列中已經存在的任何 I/O 事件回調之后對函數進行排隊,請使用setImmediate 使用process.nextTick有效地將函數排在事件隊列的頭部,以便在當前函數完成后立即執行。

因此,如果您嘗試使用遞歸分解長時間運行的、受 CPU 限制的作業,您現在需要使用setImmediate而不是process.nextTick來排隊下一次迭代,否則任何 I/O 事件回調都不會沒有機會在迭代之間運行。

舉例說明:

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('setTimeout 1'), 0);
    setImmediate(() => console.log('setImmediate 1'));
    process.nextTick(() => console.log('nextTick 1'));
    setImmediate(() => console.log('setImmediate 2'));
    process.nextTick(() => console.log('nextTick 2'));
    http.get(options, () => console.log('network IO'));
    fs.readdir(process.cwd(), () => console.log('file system IO 1'));
    setImmediate(() => console.log('setImmediate 3'));
    process.nextTick(() => console.log('nextTick 3'));
    setImmediate(() => console.log('setImmediate 4'));
    fs.readdir(process.cwd(), () => console.log('file system IO 2'));
    console.log('End');
    setTimeout(done, 1500);
  });
});

將給出以下輸出

Start // synchronous
End // synchronous
nextTick 1 // microtask
nextTick 2 // microtask
nextTick 3 // microtask
setTimeout 1 // macrotask
file system IO 1 // macrotask
file system IO 2 // macrotask
setImmediate 1 // macrotask
setImmediate 2 // macrotask
setImmediate 3 // macrotask
setImmediate 4 // macrotask
network IO // macrotask

我希望這可以幫助理解差異。

更新:

使用process.nextTick()延遲的回調會在任何其他 I/O 事件被觸發之前運行,而使用 setImmediate() 時,執行會在隊列中已經存在的任何 I/O 事件之后排隊。

Node.js 設計模式,Mario Casciaro(可能是關於 node.js/js 的最佳書籍)

我想我可以很好地說明這一點。 由於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/

請注意,檢查階段緊接在輪詢階段之后。 這是因為輪詢階段和 I/O 回調是您對setImmediate的調用最有可能運行的地方。 所以理想情況下,這些調用中的大多數實際上都是非常直接的,只是不像nextTick那樣直接,后者在每次操作后檢查並且在技術上存在於事件循環之外。

讓我們看一個setImmediateprocess.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);

現在我們已經將遞歸調用nextTick step程序中,事情將以不同的順序運行。 我們的事件循環的第一次迭代運行並調用step注冊一個setImmedaite處理程序以及一個nextTick處理程序。 當前操作結束后,我們的nextTick處理程序觸發,它遞歸調用step並注冊另一個setImmediate處理程序以及另一個nextTick處理程序。 由於nextTick處理程序在當前操作之后觸發,因此在nextTick處理程序中注冊nextTick處理程序將導致第二個處理程序在當前處理程序操作完成后立即運行。 nextTick處理程序將繼續觸發,阻止當前事件循環繼續。 在看到單個setImmediate處理程序觸發之前,我們將通過所有的nextTick處理程序。

上述代碼的輸出最終為:

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了解工具和詳細信息

簡單來說, 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

簡化的事件循環事件

這里有一些很好的答案,詳細說明了它們是如何工作的。

只需添加一個回答特定問題的答案:

什么時候應該使用nextTick什么時候應該使用setImmediate


始終使用setImmediate


Node.js 事件循環、計時器和process.nextTick()文檔包括以下內容:

我們建議開發人員在所有情況下都使用setImmediate() ,因為它更容易推理(並且它導致代碼與更廣泛的環境兼容,例如瀏覽器 JS。)


在文檔的前面,它警告process.nextTick可能導致...

一些不好的情況,因為它允許您通過進行遞歸process.nextTick()調用來“餓死”您的 I/O ,這會阻止事件循環到達輪詢階段。

事實證明, 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() ”。

我建議您查看專用於 Loop 的文檔部分以獲得更好的理解。 從那里截取的一些片段:

就用戶而言,我們有兩個類似的調用,但它們的名稱令人困惑。

  • process.nextTick() 在同一階段立即觸發

  • setImmediate() 在以下迭代或“滴答”時觸發
    事件循環

本質上,名稱應該互換。 process.nextTick() 比 setImmediate() 更立即觸發,但這是過去的產物,不太可能改變。

您永遠不應該使用process.nextTick()來中斷 CPU 繁重的操作

你永遠不應該使用 process.nextTick() 來分解這樣的工作。 這樣做會導致一個永遠不會清空的微任務隊列——你的應用程序將永遠被困在同一個階段! —— 托馬斯·亨特。 使用 Node.js 的分布式系統

事件循環的每個階段都包含幾個回調:

循環事件的一階段

一旦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()沒有相同的副作用。

在檢查階段進行setImmediate()調用會將回調添加到下一個事件循環迭代的檢查階段隊列,而不是當前階段的隊列。

案例可能使用process.nextTick

使函數處理全部異步。

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

—— 托馬斯·亨特。 使用 Node.js 的分布式系統

在這種情況下,使用setImmediate()process.nextTick()都可以; 只要確保您不會意外引入遞歸即可。

使用process.nextTick() ,您的函數將異步調用其回調。 使用process.nextTick()延遲的回調稱為微任務(process.nextTick、Promises、 queueMicrotask 、MutationObserver),它們在當前操作完成后立即執行,甚至在任何其他 I/O 事件被觸發之前。

使用 setImmediate(),在處理完所有 I/O 事件之后的事件循環階段將執行排隊。 由於process.nextTick()在任何已調度的 I/O 之前運行,它會執行得更快,但在某些情況下,它也可能無限期延遲任何 I/O 回調的運行(也稱為 I/O 飢餓),例如就像存在遞歸調用一樣。 如果回調被無限期延遲,就會發生飢餓。 setImmediate()永遠不會發生這種情況。

使用setTimeout(callback, 0)具有與setImmediate()相當的行為,使用setImmediate()調度的回調比使用setTimeout(callback,0)調度的回調執行得更快。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM