您好,登錄后才能下訂單哦!
小編給大家分享一下Node.js中的定時器是什么,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
timer 用于安排函數在未來某個時間點被調用,Node.js 中的定時器函數實現了與 Web 瀏覽器提供的定時器 API 類似的 API,但是使用了事件循環實現,Node.js 中有四個相關的方法
setTimeout(callback, delay[, ...args])
setInterval(callback[, ...args])
setImmediate(callback[, ...args])
process.nextTick(callback[, ...args])
前兩個含義和 web 上的是一致的,后兩個是 Node.js 獨有的,效果看起來就是 setTimeout(callback, 0),在 Node.js 編程中使用的最多
Node.js 不保證回調被觸發的確切時間,也不保證它們的順序,回調會在盡可能接近指定的時間被調用。setTimeout 當 delay 大于 2147483647 或小于 1 時,則 delay 將會被設置為 1, 非整數的 delay 會被截斷為整數
看一個示例,用幾種方法分別異步打印一個數字
setImmediate(console.log, 1); setTimeout(console.log, 1, 2); Promise.resolve(3).then(console.log); process.nextTick(console.log, 4); console.log(5);
會打印 5 4 3 2 1 或者 5 4 3 1 2
第五行是同步執行,其它都是異步的
setImmediate(console.log, 1); setTimeout(console.log, 1, 2); Promise.resolve(3).then(console.log); process.nextTick(console.log, 4); /****************** 同步任務和異步任務的分割線 ********************/ console.log(5);
所以先打印 5,這個很好理解,剩下的都是異步操作,Node.js 按照什么順序執行呢?
Node.js 啟動后會初始化事件輪詢,過程中可能處理異步調用、定時器調度和 process.nextTick(),然后開始處理event loop。官網中有這樣一張圖用來介紹 event loop 操作順序
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
event loop 的每個階段都有一個任務隊列,當 event loop 進入給定的階段時,將執行該階段的任務隊列,直到隊列清空或執行的回調達到系統上限后,才會轉入下一個階段,當所有階段被順序執行一次后,稱 event loop 完成了一個 tick
異步操作都被放到了下一個 event loop tick 中,process.nextTick 在進入下一次 event loop tick 之前執行,所以肯定在其它異步操作之前
setImmediate(console.log, 1); setTimeout(console.log, 1, 2); Promise.resolve(3).then(console.log); /****************** 下次 event loop tick 分割線 ********************/ process.nextTick(console.log, 4); /****************** 同步任務和異步任務的分割線 ********************/ console.log(5);
各個階段主要任務
timers:執行 setTimeout、setInterval 回調
pending callbacks:執行 I/O(文件、網絡等) 回調
idle, prepare:僅供系統內部調用
poll:獲取新的 I/O 事件,執行相關回調,在適當條件下把阻塞 node
check:setImmediate 回調在此階段執行
close callbacks:執行 socket 等的 close 事件回調
日常開發中絕大部分異步任務都是在 timers、poll、check 階段處理的
Node.js 會在 timers 階段檢查是否有過期的 timer,如果存在則把回調放到 timer 隊列中等待執行,Node.js 使用單線程,受限于主線程空閑情況和機器其它進程影響,并不能保證 timer 按照精確時間執行
定時器主要有兩種
Immediate
Timeout
Immediate 類型的計時器回調會在 check 階段被調用,Timeout 計時器會在設定的時間過期后盡快的調用回調,但
setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });
多次執行會發現打印的順序不一樣
poll 階段主要有兩個任務
計算應該阻塞和輪詢 I/O 的時間
然后,處理 poll 隊列里的事件
當event loop進入 poll 階段且沒有被調度的計時器時
一旦 poll 隊列為空,event loop 將檢查 timer 隊列是否為空,如果非空則進入下一輪 event loop
上面提到了如果在不同的 I/O 里,不能確定 setTimeout 和 setImmediate 的執行順序,但如果 setTimeout 和 setImmediate 在一個 I/O 回調里,肯定是 setImmediate 先執行,因為在 poll 階段檢查到有 setImmediate() 任務,event loop 直接進入 check 階段執行 setImmediate 回調
const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); });
在該階段執行 setImmediate 回調
前端同學肯定都聽說過 micoTask 和 macroTask,Promise.then 屬于 microTask,在瀏覽器環境下 microTask 任務會在每個 macroTask 執行最末端調用
在 Node.js 環境下 microTask 會在每個階段完成之間調用,也就是每個階段執行最后都會執行一下 microTask 隊列
setImmediate(console.log, 1); setTimeout(console.log, 1, 2); /****************** microTask 分割線 ********************/ Promise.resolve(3).then(console.log); // microTask 分割線 /****************** 下次 event loop tick 分割線 ********************/ process.nextTick(console.log, 4); /****************** 同步任務和異步任務的分割線 ********************/ console.log(5);
setImmediate 聽起來是立即執行,process.nextTick 聽起來是下一個時鐘執行,為什么效果是反過來的?這就要從那段不堪回首的歷史講起
最開始的時候只有 process.nextTick 方法,沒有 setImmediate 方法,通過上面的分析可以看出來任何時候調用 process.nextTick(),nextTick 會在 event loop 之前執行,直到 nextTick 隊列被清空才會進入到下一 event loop,如果出現 process.nextTick 的遞歸調用程序沒有被正確結束,那么 IO 的回調將沒有機會被執行
const fs = require('fs'); fs.readFile('a.txt', (err, data) => { console.log('read file task done!'); }); let i = 0; function test(){ if(i++ < 999999) { console.log(`process.nextTick ${i}`); process.nextTick(test); } } test();
執行程序將返回
nextTick 1 nextTick 2 ... ... nextTick 999999 read file task done!
于是乎需要一個不這么 bug 的調用,setImmediate 方法出現了,比較令人費解的是在 process.nextTick 起錯名字的情況下,setImmediate 也用了一個錯誤的名字以示區分。。。
那么是不是編程中應該杜絕使用 process.nextTick 呢?官方推薦大部分時候應該使用 setImmediate,同時對 process.nextTick 的最大調用堆棧做了限制,但 process.nextTick 的調用機制確實也能為我們解決一些棘手的問題
允許用戶在 even tloop 開始之前 處理異常、執行清理任務
允許回調在調用棧 unwind 之后,下次 event loop 開始之前執行
一個類繼承了 EventEmitter,而且想在實例化的時候觸發一個事件
const EventEmitter = require('events'); const util = require('util'); function MyEmitter() { EventEmitter.call(this); this.emit('event'); } util.inherits(MyEmitter, EventEmitter); const myEmitter = new MyEmitter(); myEmitter.on('event', () => { console.log('an event occurred!'); });
在構造函數執行 this.emit('event')
會導致事件觸發比事件回調函數綁定早,使用 process.nextTick 可以輕松實現預期效果
const EventEmitter = require('events'); const util = require('util'); function MyEmitter() { EventEmitter.call(this); // use nextTick to emit the event once a handler is assigned process.nextTick(() => { this.emit('event'); }); } util.inherits(MyEmitter, EventEmitter); const myEmitter = new MyEmitter(); myEmitter.on('event', () => { console.log('an event occurred!'); });
以上是“Node.js中的定時器是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。