首頁  >  文章  >  web前端  >  Node.js中使用計時器定時執行函數詳解_node.js

Node.js中使用計時器定時執行函數詳解_node.js

WBOY
WBOY原創
2016-05-16 16:39:341357瀏覽

如果你熟悉客戶端JavaScript編程,你可能使用過setTimeout和setInterval函數,這兩個函數允許延時一段時間再運行函數。例如下面的程式碼, 一旦被載入到Web頁面,1秒後會在頁面文件後追加「Hello there」:

複製程式碼 程式碼如下:

var oneSecond = 1000 * 1; // one second = 1000 x 1 ms

setTimeout(function() {

    document.write('

Hello there.

');

}, oneSecond);

而setInterval允許以指定的時間間隔重複執行函數。如果把下面的程式碼注入到Web頁面,會導致每秒鐘在頁面文件後面追加一句「Hello there」:

複製程式碼 程式碼如下:

                  var oneSecond = 1000 * 1; // one second = 1000 x 1 ms

                  setInterval(function() {

                                  

                  }, oneSecond);


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

使用setTimeout延遲函數執行

setTimeout可以製定一個在將來某個時間把指定函數運行一次的執行計劃,例如:

複製程式碼 程式碼如下:
                   var timeout_ms = 2000; // 2 seconds
                   var timeout = setTimeout(function() {

                            console.log("timed out!");

                   }, timeout_ms);


和客戶端JavaScript完全一樣,setTimeout接受兩個參數,第一個參數是需要被延遲的函數,第二個參數是延遲時間(以毫秒為單位)。

setTimeout回傳一個超時句柄,它是個內部對象,可以用它作為參數呼叫clearTimeout來取消計時器,除此之外這個句柄沒有任何作用。

使用clearTimeout取消執行計畫

一旦獲得了超時句柄,就可以用clearTimeout來取消函數執行計劃,像這樣:


複製程式碼 程式碼如下:
                   var timeoutTime = 1000; // one second
                   var timeout = setTimeout(function() {

                            console.log("timed out!");

                   }, timeoutTime);

                   clearTimeout(timeout);


 這個例子裡,計時器永遠不會被觸發,也不會輸出」time out!」這幾個字。你也可以在將來的任何時間取消執行計劃,就像下面的例子:

複製程式碼 程式碼如下:

 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」:

複製程式碼 程式碼如下:

                   var period = 1000; // 1 second

                   setInterval(function() {

                            console.log("tick");

                   }, period);

如果你不想讓它永遠運作下去,可以用clearInterval()取消計時器。

setInterval回傳一個執行計畫句柄,可以把它當作clearInterval的參數來取消執行計畫:

複製程式碼 程式碼如下:

                   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時間來衡量)。

你可以像下面這樣,把函數延遲到下一輪事件循環再運行:

複製程式碼 程式碼如下:

                   process.nextTick(function() {

                           my_expensive_computation_function();

                   });

  注意:process物件是Node少數的全域物件之一。

阻塞事件循環

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

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

看下面阻塞事件循環的例子:

複製程式碼 程式碼如下:

                   process.nextTick(function nextTick1() {

                            var a = 0;

                            while(true) {

                                  

                            }

                   });

                   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操作,你可以這樣延遲它:


複製程式碼 程式碼如下:
                   stream.on("data", function(data) {
                           stream.end("my response");

                            process.nextTick(function() {

                                   

                            });

                   });


使用setTimeout取代setInterval來確保函數執行的串列性

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

複製程式碼 程式碼如下:

                   var interval = 1000;

                   setInterval(function() {

                            my_async_function(function() {

                                   

                            });

                   },interval);//譯者註:前面「,interval」是我新增的,作者應該是錯誤遺漏了


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

譯者註:(下方粗體部分為譯者添加,非原書內容)

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

複製程式碼 程式碼如下:
                   var interval = 1000;
                   setInterval(function(){

                            (function my_async_function(){

                               

                                  

                                  

                           })();

                   },interval);

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

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


複製程式碼

程式碼如下:

                    var interval = 1000; // 1 秒

                   (function schedule() {      //第3行

                           以setTimeout(function do_it() {

                                  

                                  

                                  

                                  

                            }, interval);

                   }());        //第10排

 

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

小結

可以用setTimeout()函數預先設定函數的執行計劃,並用clearTimeout()函數取消它。也可以用setInterval()週期性的重複執行某個函數,對應的,可以使用clearInterval()取消這個重複執行計劃。 如果因為使用了一個處理器敏感的操作而堵塞了事件循環,那些原計劃應該被執行的函數將會被延遲,甚至永遠無法執行。所以不要在事件循環內使用CPU敏感的操作。還有,你可以用process.nextTick()把函數的執行延遲到事件循環的下一輪。

I/O和setInterval()一起使用時,你無法保證在任何時間點只有一個掛起的調用,但是,你可以使用遞歸函數和setTimeout()函數來迴避這個棘手的問題。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn