Node.js使用計時器定時執行函式詳解

螞蟻小編發表於2017-03-27

只要稍稍有javascript的使用經驗,那麼對於setTimeout()和setInterval()定時器函式不會陌生,這兩個函式可以實現延遲執行指定的函式,當然這兩個區別還是很大的,更多內容可以參閱下面相關程式碼:

setTimeout()函式可以參閱window.setTimeout()方法一章節。 

setInterval()函式可以參閱window.setInterval()方法一章節。

程式碼例項:

[JavaScript] 純文字檢視 複製程式碼
var oneSecond=1000 * 1; 
setTimeout(function(){
  console.log('螞蟻部落');
},oneSecond);

上面的程式碼可以再一秒後輸出相關的內容。

因為Web早已成為一個用來構建應用程式的平臺,而不再是簡單的靜態頁面,所以這種類似的需求日益浮現。這些任務計劃函式幫助開發人員實現表單定期驗證,延遲遠端資料同步,或者那些需要延時反應的UI互動。Node也完整實現了這些方法。在伺服器端,你可以用它們來重複或延遲執行很多工,比如快取過期,連線池清理,會話過期,輪詢等等。

使用setTimeout延遲函式執行:

一旦獲得了超時控制程式碼,就可以用clearTimeout來取消函式執行計劃,像這樣:

[JavaScript] 純文字檢視 複製程式碼
var timeoutTime=1000;
var timeout=setTimeout(function() {
  console.log("timed out!");
},timeoutTime);
clearTimeout(timeout);

計時器永遠不會被觸發,也不會輸出”time out!”這幾個字。也可以在將來的任何時間取消執行計劃,例項如下:

[JavaScript] 純文字檢視 複製程式碼
var timeout=setTimeout(function A(){
  console.log("timed out!");
},2000);
 
 
setTimeout(function B(){
  clearTimeout(timeout);
},1000);

程式碼指定了兩個延時執行的函式A和B,函式A計劃在2秒鐘後執行,B計劃在1秒鐘後執行,因為函式B先執行,而它取消了A的執行計劃,因此A永遠不會執行。

制定和取消函式的重複執行計劃:

setInterval和setTimeout類似,但是它會以指定時間為間隔重複執行一個函式。你可以用它來週期性的觸發一段程式,來完成一些類似清理,收集,日誌,獲取資料,輪詢等其它需要重複執行的任務。

下面程式碼每秒會向控制檯輸出一句"tick":

[JavaScript] 純文字檢視 複製程式碼
var period=1000;
setInterval(function(){
  console.log("tick");
}, period);

如果不想讓它永遠執行下去,可以用clearInterval()取消定時器。

setInterval返回一個執行計劃控制程式碼,可以把它用作clearInterval的引數來取消執行計劃:

[JavaScript] 純文字檢視 複製程式碼
var interval=setInterval(function(){
  console.log("tick");
},1000);
clearInterval(interval);

使用process.nextTick將函式執行延遲到事件迴圈的下一輪:

有時候客戶端JavaScript程式設計師用setTimeout(callback,0)將任務延遲一段很短的時間,第二個引數是0毫秒,它告訴JavaScript執行時,當所有掛起的事件處理完畢後立刻執行這個回撥函式。有時候這種技術被用來延遲執行一些並不需要被立刻執行的操作。比如,有時候需要在使用者事件處理完畢後再開始播放動畫或者做一些其它的計算。

Node中,就像 “事件迴圈”的字面意思,事件迴圈執行在一個處理事件佇列的迴圈裡,事件迴圈工作過程中的每一輪就稱為一個tick。

你可以在事件迴圈每次開始下一輪(下一個tick)執行時呼叫回撥函式一次,這也正是process.nextTick的原理,而setTimeout,setTimeout使用JavaScript執行時內部的執行佇列,而不是使用事件迴圈。

通過使用process.nextTick(callback) ,而不是setTimeout(callback, 0),你的回撥函式會在佇列內的事件處理完畢後立刻執行,它要比JavaScript的超時佇列快很多(以CPU時間來衡量)。

你可以像下面這樣,把函式延遲到下一輪事件迴圈再執行:

[JavaScript] 純文字檢視 複製程式碼
process.nextTick(function(){
  my_expensive_computation_function();
});

堵塞事件迴圈:

Node和JavaScript的執行時採用的是單執行緒事件迴圈,每次迴圈,執行時通過呼叫相關回撥函式來處理佇列內的下個事件。當事件執行完畢,事件迴圈取得執行結果並處理下個事件,如此反覆,直到事件佇列為空。如果其中一個回撥函式執行時佔用了很長時間,事件迴圈在那期間就不能處理其它掛起的事件,這會讓應用程式或服務變得非常慢。

在處理事件時,如果使用了記憶體敏感或者處理器敏感的函式,會導致事件迴圈變得緩慢,而且造成大量事件堆積,不能被及時處理,甚至堵塞佇列。

看下面堵塞事件迴圈的例子:

[JavaScript] 純文字檢視 複製程式碼
process.nextTick(function nextTick1() {
  var a = 0;
  while(true){
    a++;
  }
});
process.nextTick(function nextTick2(){
  console.log("next tick");
});
setTimeout(function timeout(){
  console.log("timeout");
},1000);

這個例子裡,nextTick2和timeout函式無論等待多久都沒機會執行,因為事件迴圈被nextTick函式裡的無限迴圈堵塞了,即使timeout函式被計劃在1秒鐘後執行它也不會執行。

當使用setTimeout時,回撥函式會被新增到執行計劃佇列,而在這個例子裡它們甚至不會被新增到佇列。這雖然是個極端例子,但是你可以看到,執行一個處理器敏感的任務時可能會堵塞或者拖慢事件迴圈。

退出事件迴圈:

使用process.nextTick,可以把一個非關鍵性的任務推遲到事件迴圈的下一輪(tick)再執行,這樣可以釋放事件迴圈,讓它可以繼續執行其它掛起的事件。

看下面例子,如果你打算刪除一個臨時檔案,但是又不想讓data事件的回撥函式等待這個IO操作,你可以這樣延遲它:

[JavaScript] 純文字檢視 複製程式碼
stream.on("data",function(data){
  stream.end("my response");
  process.nextTick(function(){
    fs.unlink("/path/to/file");
  });
});

使用setTimeout替代setInterval來確保函式執行的序列性:

假設,打算設計一個叫my_async_function的函式,它可以做某些I/O操作(比如解析日誌檔案)的函式,並打算讓它週期性執行,你可以用setInterval這樣實現它:

[JavaScript] 純文字檢視 複製程式碼
var interval=1000;
setInterval(function(){
  my_async_function(function(){
    console.log('my_async_function finished!');
  });
},interval);

必須能確保這些函式不會被同時執行,但是如果使用setinterval你無法保證這一點,假如my_async_function函式執行的時間比interval變數多了一毫秒,它們就會被同時執行,而不是按次序序列執行。

為了方便理解這部分內容,可以修改下作者的程式碼,讓它可以實際執行:

[JavaScript] 純文字檢視 複製程式碼
var interval=1000;
setInterval(function(){
  (function my_async_function(){
    setTimeout(function(){
      console.log("1");
    },5000);
 })();
},interval);

執行下這段程式碼看看,你會發現,等待5秒鐘後,“hello ”被每隔1秒輸出一次。而我們期望是,當前my_async_function執行完畢(耗費5秒)後,等待1秒再執行下一個my_async_function,每次輸出之間應該間隔6秒才對。造成這種結果,是因為my_async_function不是序列執行的,而是多個在同時執行。

因此,你需要一種辦法來強制使一個my_async_function執行結束到下個my_async_function開始執行之間的間隔時間正好是interval變數指定的時間。你可以這樣做:

[JavaScript] 純文字檢視 複製程式碼
var interval=1000;
(function schedule(){
  setTimeout(function do_it(){
    my_async_function(function(){
      console.log('async is done!');
      schedule();
    });
  }, interval);
}());

前面程式碼裡,宣告瞭一個叫schedule的函式(第3行),並且在宣告後立刻呼叫它(第10行),schedule函式會在1秒(由interval指定)後執行do_it函式。1秒鐘過後,第5行的my_async_function函式會被呼叫,當它執行完畢後,會呼叫它自己的那個匿名回撥函式(第6行),而這個匿名回撥函式又會再次重置do_it的執行計劃,讓它1秒鐘後重新執行,這樣程式碼就開始序列地不斷迴圈執行了。

相關文章