相關推薦:《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 按照什麼順序執行呢?
┌───────────────────────────┐ ┌─>│ 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 事件回呼
定時器主要有兩種
check 階段被調用,Timeout 計時器會在設定的時間過期後儘快的調用回調,但
setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });多次執行會發現列印的順序不一樣
##poll
如果poll 隊列不是空的,event loop 將循環訪問回調隊列並同步執行,直到隊列已用盡或達到了系統或達到最大回呼數
上面提到如果在不同的I/O 裡,不能確定 setTimeout 和setImmediate 的執行順序,但如果setTimeout 和setImmediate 在一個I/O 回調裡,肯定是setImmediate 先執行,因為在poll 階段檢查到有 setImmediate() 任務,event loop 直接進入check 階段執行回調setImmediate event loop 直接進入check 階段執行回調setImmediate
const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); });
前端同学肯定都听说过 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中的定時器的詳細內容。更多資訊請關注PHP中文網其他相關文章!