小編給大家分享一下Node.js中的定時器是什么,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
創(chuàng)新互聯(lián)長期為數(shù)千家客戶提供的網(wǎng)站建設(shè)服務(wù),團隊從業(yè)經(jīng)驗10年,關(guān)注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為廬江企業(yè)提供專業(yè)的成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計,廬江網(wǎng)站改版等技術(shù)服務(wù)。擁有10年豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。
timer 用于安排函數(shù)在未來某個時間點被調(diào)用,Node.js 中的定時器函數(shù)實現(xiàn)了與 Web 瀏覽器提供的定時器 API 類似的 API,但是使用了事件循環(huán)實現(xiàn),Node.js 中有四個相關(guān)的方法
setTimeout(callback, delay[, ...args])
setInterval(callback[, ...args])
setImmediate(callback[, ...args])
process.nextTick(callback[, ...args])
前兩個含義和 web 上的是一致的,后兩個是 Node.js 獨有的,效果看起來就是 setTimeout(callback, 0),在 Node.js 編程中使用的最多
Node.js 不保證回調(diào)被觸發(fā)的確切時間,也不保證它們的順序,回調(diào)會在盡可能接近指定的時間被調(diào)用。setTimeout 當(dāng) delay 大于 2147483647 或小于 1 時,則 delay 將會被設(shè)置為 1, 非整數(shù)的 delay 會被截斷為整數(shù)
看一個示例,用幾種方法分別異步打印一個數(shù)字
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
第五行是同步執(zhí)行,其它都是異步的
setImmediate(console.log, 1); setTimeout(console.log, 1, 2); Promise.resolve(3).then(console.log); process.nextTick(console.log, 4); /****************** 同步任務(wù)和異步任務(wù)的分割線 ********************/ console.log(5);
所以先打印 5,這個很好理解,剩下的都是異步操作,Node.js 按照什么順序執(zhí)行呢?
Node.js 啟動后會初始化事件輪詢,過程中可能處理異步調(diào)用、定時器調(diào)度和 process.nextTick(),然后開始處理event loop。官網(wǎng)中有這樣一張圖用來介紹 event loop 操作順序
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
event loop 的每個階段都有一個任務(wù)隊列,當(dāng) event loop 進入給定的階段時,將執(zhí)行該階段的任務(wù)隊列,直到隊列清空或執(zhí)行的回調(diào)達到系統(tǒng)上限后,才會轉(zhuǎn)入下一個階段,當(dāng)所有階段被順序執(zhí)行一次后,稱 event loop 完成了一個 tick
異步操作都被放到了下一個 event loop tick 中,process.nextTick 在進入下一次 event loop tick 之前執(zhí)行,所以肯定在其它異步操作之前
setImmediate(console.log, 1); setTimeout(console.log, 1, 2); Promise.resolve(3).then(console.log); /****************** 下次 event loop tick 分割線 ********************/ process.nextTick(console.log, 4); /****************** 同步任務(wù)和異步任務(wù)的分割線 ********************/ console.log(5);
各個階段主要任務(wù)
timers:執(zhí)行 setTimeout、setInterval 回調(diào)
pending callbacks:執(zhí)行 I/O(文件、網(wǎng)絡(luò)等) 回調(diào)
idle, prepare:僅供系統(tǒng)內(nèi)部調(diào)用
poll:獲取新的 I/O 事件,執(zhí)行相關(guān)回調(diào),在適當(dāng)條件下把阻塞 node
check:setImmediate 回調(diào)在此階段執(zhí)行
close callbacks:執(zhí)行 socket 等的 close 事件回調(diào)
日常開發(fā)中絕大部分異步任務(wù)都是在 timers、poll、check 階段處理的
Node.js 會在 timers 階段檢查是否有過期的 timer,如果存在則把回調(diào)放到 timer 隊列中等待執(zhí)行,Node.js 使用單線程,受限于主線程空閑情況和機器其它進程影響,并不能保證 timer 按照精確時間執(zhí)行
定時器主要有兩種
Immediate
Timeout
Immediate 類型的計時器回調(diào)會在 check階段被調(diào)用,Timeout 計時器會在設(shè)定的時間過期后盡快的調(diào)用回調(diào),但
setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });
多次執(zhí)行會發(fā)現(xiàn)打印的順序不一樣
poll 階段主要有兩個任務(wù)
計算應(yīng)該阻塞和輪詢 I/O 的時間
然后,處理 poll 隊列里的事件
當(dāng)event loop進入 poll 階段且沒有被調(diào)度的計時器時
一旦 poll隊列為空,event loop 將檢查 timer 隊列是否為空,如果非空則進入下一輪 event loop
上面提到了如果在不同的 I/O 里,不能確定 setTimeout 和 setImmediate 的執(zhí)行順序,但如果 setTimeout 和 setImmediate 在一個 I/O 回調(diào)里,肯定是 setImmediate 先執(zhí)行,因為在 poll 階段檢查到有 setImmediate() 任務(wù),event loop 直接進入 check 階段執(zhí)行 setImmediate 回調(diào)
const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); });
在該階段執(zhí)行 setImmediate 回調(diào)
前端同學(xué)肯定都聽說過 micoTask 和 macroTask,Promise.then 屬于 microTask,在瀏覽器環(huán)境下 microTask 任務(wù)會在每個 macroTask 執(zhí)行最末端調(diào)用
在 Node.js 環(huán)境下 microTask 會在每個階段完成之間調(diào)用,也就是每個階段執(zhí)行最后都會執(zhí)行一下 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); /****************** 同步任務(wù)和異步任務(wù)的分割線 ********************/ console.log(5);
setImmediate 聽起來是立即執(zhí)行,process.nextTick 聽起來是下一個時鐘執(zhí)行,為什么效果是反過來的?這就要從那段不堪回首的歷史講起
最開始的時候只有 process.nextTick 方法,沒有 setImmediate 方法,通過上面的分析可以看出來任何時候調(diào)用 process.nextTick(),nextTick 會在 event loop 之前執(zhí)行,直到 nextTick 隊列被清空才會進入到下一 event loop,如果出現(xiàn) process.nextTick 的遞歸調(diào)用程序沒有被正確結(jié)束,那么 IO 的回調(diào)將沒有機會被執(zhí)行
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();
執(zhí)行程序?qū)⒎祷?/p>
nextTick 1 nextTick 2 ... ... nextTick 999999 read file task done!
于是乎需要一個不這么 bug 的調(diào)用,setImmediate 方法出現(xiàn)了,比較令人費解的是在 process.nextTick 起錯名字的情況下,setImmediate 也用了一個錯誤的名字以示區(qū)分。。。
那么是不是編程中應(yīng)該杜絕使用 process.nextTick 呢?官方推薦大部分時候應(yīng)該使用 setImmediate,同時對 process.nextTick 的最大調(diào)用堆棧做了限制,但 process.nextTick 的調(diào)用機制確實也能為我們解決一些棘手的問題
允許用戶在 even tloop 開始之前 處理異常、執(zhí)行清理任務(wù)
允許回調(diào)在調(diào)用棧 unwind 之后,下次 event loop 開始之前執(zhí)行
一個類繼承了 EventEmitter,而且想在實例化的時候觸發(fā)一個事件
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!'); });
在構(gòu)造函數(shù)執(zhí)行 this.emit('event')
會導(dǎo)致事件觸發(fā)比事件回調(diào)函數(shù)綁定早,使用 process.nextTick 可以輕松實現(xiàn)預(yù)期效果
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中的定時器是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!