您好,登錄后才能下訂單哦!
小編給大家分享一下Node.js中的定時(shí)器是什么,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
timer 用于安排函數(shù)在未來某個(gè)時(shí)間點(diǎn)被調(diào)用,Node.js 中的定時(shí)器函數(shù)實(shí)現(xiàn)了與 Web 瀏覽器提供的定時(shí)器 API 類似的 API,但是使用了事件循環(huán)實(shí)現(xiàn),Node.js 中有四個(gè)相關(guān)的方法
setTimeout(callback, delay[, ...args])
setInterval(callback[, ...args])
setImmediate(callback[, ...args])
process.nextTick(callback[, ...args])
前兩個(gè)含義和 web 上的是一致的,后兩個(gè)是 Node.js 獨(dú)有的,效果看起來就是 setTimeout(callback, 0),在 Node.js 編程中使用的最多
Node.js 不保證回調(diào)被觸發(fā)的確切時(shí)間,也不保證它們的順序,回調(diào)會在盡可能接近指定的時(shí)間被調(diào)用。setTimeout 當(dāng) delay 大于 2147483647 或小于 1 時(shí),則 delay 將會被設(shè)置為 1, 非整數(shù)的 delay 會被截?cái)酁檎麛?shù)
看一個(gè)示例,用幾種方法分別異步打印一個(gè)數(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,這個(gè)很好理解,剩下的都是異步操作,Node.js 按照什么順序執(zhí)行呢?
Node.js 啟動后會初始化事件輪詢,過程中可能處理異步調(diào)用、定時(shí)器調(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 的每個(gè)階段都有一個(gè)任務(wù)隊(duì)列,當(dāng) event loop 進(jìn)入給定的階段時(shí),將執(zhí)行該階段的任務(wù)隊(duì)列,直到隊(duì)列清空或執(zhí)行的回調(diào)達(dá)到系統(tǒng)上限后,才會轉(zhuǎn)入下一個(gè)階段,當(dāng)所有階段被順序執(zhí)行一次后,稱 event loop 完成了一個(gè) tick
異步操作都被放到了下一個(gè) event loop tick 中,process.nextTick 在進(jìn)入下一次 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);
各個(gè)階段主要任務(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 隊(duì)列中等待執(zhí)行,Node.js 使用單線程,受限于主線程空閑情況和機(jī)器其它進(jìn)程影響,并不能保證 timer 按照精確時(shí)間執(zhí)行
定時(shí)器主要有兩種
Immediate
Timeout
Immediate 類型的計(jì)時(shí)器回調(diào)會在 check 階段被調(diào)用,Timeout 計(jì)時(shí)器會在設(shè)定的時(shí)間過期后盡快的調(diào)用回調(diào),但
setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });
多次執(zhí)行會發(fā)現(xiàn)打印的順序不一樣
poll 階段主要有兩個(gè)任務(wù)
計(jì)算應(yīng)該阻塞和輪詢 I/O 的時(shí)間
然后,處理 poll 隊(duì)列里的事件
當(dāng)event loop進(jìn)入 poll 階段且沒有被調(diào)度的計(jì)時(shí)器時(shí)
一旦 poll 隊(duì)列為空,event loop 將檢查 timer 隊(duì)列是否為空,如果非空則進(jìn)入下一輪 event loop
上面提到了如果在不同的 I/O 里,不能確定 setTimeout 和 setImmediate 的執(zhí)行順序,但如果 setTimeout 和 setImmediate 在一個(gè) I/O 回調(diào)里,肯定是 setImmediate 先執(zhí)行,因?yàn)樵?poll 階段檢查到有 setImmediate() 任務(wù),event loop 直接進(jìn)入 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ù)會在每個(gè) macroTask 執(zhí)行最末端調(diào)用
在 Node.js 環(huán)境下 microTask 會在每個(gè)階段完成之間調(diào)用,也就是每個(gè)階段執(zhí)行最后都會執(zhí)行一下 microTask 隊(duì)列
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 聽起來是下一個(gè)時(shí)鐘執(zhí)行,為什么效果是反過來的?這就要從那段不堪回首的歷史講起
最開始的時(shí)候只有 process.nextTick 方法,沒有 setImmediate 方法,通過上面的分析可以看出來任何時(shí)候調(diào)用 process.nextTick(),nextTick 會在 event loop 之前執(zhí)行,直到 nextTick 隊(duì)列被清空才會進(jìn)入到下一 event loop,如果出現(xiàn) process.nextTick 的遞歸調(diào)用程序沒有被正確結(jié)束,那么 IO 的回調(diào)將沒有機(jī)會被執(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!
于是乎需要一個(gè)不這么 bug 的調(diào)用,setImmediate 方法出現(xiàn)了,比較令人費(fèi)解的是在 process.nextTick 起錯(cuò)名字的情況下,setImmediate 也用了一個(gè)錯(cuò)誤的名字以示區(qū)分。。。
那么是不是編程中應(yīng)該杜絕使用 process.nextTick 呢?官方推薦大部分時(shí)候應(yīng)該使用 setImmediate,同時(shí)對 process.nextTick 的最大調(diào)用堆棧做了限制,但 process.nextTick 的調(diào)用機(jī)制確實(shí)也能為我們解決一些棘手的問題
允許用戶在 even tloop 開始之前 處理異常、執(zhí)行清理任務(wù)
允許回調(diào)在調(diào)用棧 unwind 之后,下次 event loop 開始之前執(zhí)行
一個(gè)類繼承了 EventEmitter,而且想在實(shí)例化的時(shí)候觸發(fā)一個(gè)事件
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 可以輕松實(shí)現(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中的定時(shí)器是什么”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。