本篇文章帶大家一起來了解Node.js中的event-loop(時間循環)機制,希望對大家有幫助!
今天我們來學習下nodeJs中的event-loop。 event-loop的理解對我來說一直都是一個比較大的難點,希望透過這次的學習把這個難點突破,也希望能透過這篇部落格加深自己對event-loop的理解和印象。
libuv
在學習event-loop之前,先了解下node的libuv。 libuv負責不同作業系統上的不同I/O模型的實現,並且把不同的實作抽象化為能應用與第三方應用程式的API。
問題
在正式學習event-loop前,先思考一個問題
setTimeout(() => { console.log("timer1"); Promise.resolve().then(() => { console.log("promise1"); }); }, 0); setTimeout(() => { console.log("timer2"); Promise.resolve().then(() => { console.log("promise2"); }); }, 0);
這段程式碼在瀏覽器中運行的結果是怎樣的?
在node中運行的結果又是怎麼樣的呢?
在node8.6之前:
#node8.6之後:
為什麼會有這樣的結果,我們稍後會分析!
nodeJs 中的event-loop
首先,來看一張圖:
在圖中可以看到6個階段,分別是:timers,pending callbacks,idle/prepare,poll,check,close callbacks。
timers階段:主要執行setTimeOut,setInterval的回呼
pending callbacks階段:執行一些系統呼叫的錯誤,比如說網路通訊的錯誤回呼
idle/prepare階段:只在系統內部使用(這個階段我們控制干涉不了)
poll階段:取得新的I/O事件,例如取得一個讀取檔案的I/O回呼。 在適合的情況下,nodejs將阻塞在這個階段
check階段:執行setImmediate的回呼
例如執行sokect的destory,close事件回呼
每一個階段都遵循一個FIFO(先入先出)的規則來執行任務佇列裡面的任務。 在這六個階段中,我們著重需要關注的是timers,poll,check階段。我們日常開發中絕大部分的非同步任務都是在這三個階段處理的。
timers
我們先來談談timers階段。
timers是事件循環的第一個階段,nodejs會去檢查有沒有已經過期了的timer,如果有,就將它的回調放入佇列中。但是nodejs並不能保證timer在預設事件到了就會立即執行回調,這是因為nodejs對timer的過期檢查不一定可靠,它會受機器上其他運行程序的影響,或者是會遇到當前主線程不空閒的情況。
對於這裡的不確定性,官網上舉了一個例子:
先聲明一個setTimeOut,然後外部讀取一個文件,當讀取文件操作超過定時器的時間,這樣一來讀檔案操作就會把定時器的回調延後,這就是前面說的主執行緒不空閒的情況。
poll
poll階段主要是執行兩件事:
1、處理poll階段的任務佇列
2、當有了已經逾時的timer執行它的回呼函數
在上圖中,我們還可以看到:在poll階段執行完poll任務佇列的任務之後,會去檢查有無預設的setImmediate,如果有,則進入check階段,如果沒有,則nodejs將會阻塞在這裡。
這裡我們就會有一個疑問了,如果阻塞在poll階段,那我們設定的timer豈不是執行不了了嗎?
其實當event-loop阻塞在poll階段時,nodejs會有一個檢查機制,它會去檢查timers佇列是否為空,如果不為空,則重新進入timers階段。
check
check階段主要時執行setImmediate的回呼函數。
小总结
event-loop的每个阶段都有一个队列,当event-loop达到某个阶段之后,将执行这个阶段的任务队列,直到队列清空或者达到系统规定的最大回调限制之后,才会进入下一个阶段。当所有阶段都执行完成一次之后,称event-loop完成一个tick。
案例
上面我们说完了event-loop的理论部分,但是光有理论我们也还是不能很清晰的理解event-loop。下面我们就根据几个demo来更加深入的理解下event-loop!
demo1
const fs=require('fs') fs.readFile('test.txt',()=>{ console.log('readFile') setTimeout(()=>{ console.log('settimeout'); },0) setImmediate(()=>{ console.log('setImmediate') }) })
执行结果:
可见执行结果跟我们前面的分析时一致的!
demo2
const fs = require("fs"); const EventEmitter = require("events").EventEmitter; let pos = 0; const messenger = new EventEmitter(); messenger.on("message", function (msg) { console.log(++pos + " message:" + msg); // }); console.log(++pos + " first"); // process.nextTick(function () { console.log(++pos + " nextTick"); // }); messenger.emit("message", "hello!"); fs.stat(__filename, function () { console.log(++pos + " stat"); // }); setTimeout(function () { console.log(++pos + " quick timer"); // }, 0); setTimeout(function () { console.log(++pos + " long timer"); // }, 30); setImmediate(function () { console.log(++pos + " immediate"); // }); console.log(++pos + " last"); //
结果:
了解下浏览器和node的event-loop差异在什么地方
在node 8.6 之前:
浏览器中的微任务队列会在每个宏任务执行完成之后执行,而node中的微任务会在事件循环的各个阶段之间执行,即每个阶段执行完成之后会去执行微任务队列。
在8.6之后:
浏览器和node中微任务的执行是一致的!
所以,在文章开头,我们提出的思考的问题就有了结果。
关于 process.nextTick()和setImmediate
process.nextTick()
语法:process.nextTick(callback,agrs)
执行时机:
这个函数其实是独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行。递归的调用process.nextTick()
会导致I/O starving,官方推荐使用setImmediate()
关于starving现象的说明:
const fs = require("fs"); fs.readFile("test.txt", (err, msg) => { console.log("readFile"); }); let index = 0; function handler() { if (index >= 30) return; index++; console.log("nextTick" + index); process.nextTick(handler); } handler();
运行结果:
可以看到,等到nextTick函数呗执行30次之后,读取文件的回调才被执行!这样的现象被称为 I/O 饥饿。
当我们把 process.nextTick 换为 setImmediate
const fs = require("fs"); fs.readFile("test.txt", (err, msg) => { console.log("readFile"); }); let index = 0; function handler() { if (index >= 30) return; index++; console.log("nextTick" + index); setImmediate(handler); } handler();
结果:
造成这两种差异的原因是,嵌套调用的setImmediate的回调被排到了下一次event-loop中去!
event-loop核心思维导图
结束语
通过今天的学习,让我对event-loop的理解更深刻了。那么,下次见。好好学习,天天向上!
更多编程相关知识,请访问:编程视频!!
以上是一文聊聊Node.js中的event-loop機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

SublimeText3 Linux新版
SublimeText3 Linux最新版

Dreamweaver Mac版
視覺化網頁開發工具