js異步歷史
一個 JavaScript 引擎會常駐于內(nèi)存中,它等待著我們把JavaScript 代碼或者函數(shù)傳遞給它執(zhí)行
在 ES3 和更早的版本中,JavaScript 本身還沒有異步執(zhí)行代碼的能力,引擎就把代碼直接順次執(zhí)行了,異步任務(wù)都是宿主環(huán)境(瀏覽器)發(fā)起的(setTimeout、AJAX等)。
在 ES5 之后,JavaScript 引入了 Promise,這樣,不需要瀏覽器的安排,JavaScript 引擎本身也可以發(fā)起任務(wù)了
JS異步實現(xiàn)原理js為單線程,js引擎中負(fù)責(zé)解析執(zhí)行js代碼的線程只有一個(主線程),即每次只能做一件事,其他IO操作放入任務(wù)隊列等待執(zhí)行,異步過程中,工作線程在異步操作完成后需要通知主線程。那么這個通知機(jī)制是利用消息隊列和事件循環(huán)(EventLoop)實際上,主線程只會做一件事情,就是從消息隊列里面取消息、執(zhí)行消息,再取消息、再執(zhí)行。當(dāng)消息隊列為空時,就會等待直到消息隊列變成非空。而且主線程只有在將當(dāng)前的消息執(zhí)行完成后,才會去取下一個消息
node:node.js單線程只是一個js主線程,本質(zhì)上的異步操作還是由線程池完成的,node將所有的阻塞操作都交給了內(nèi)部的線程池去實現(xiàn),本身只負(fù)責(zé)不斷的往返調(diào)度,并沒有進(jìn)行真正的I/O操作,從而實現(xiàn)異步非阻塞I/O,這便是node單線程的精髓之處了。
消息隊列:消息隊列是一個先進(jìn)先出的隊列,它里面存放著各種消息。
事件循環(huán):事件循環(huán)是指主線程重復(fù)從消息隊列中取消息、執(zhí)行的過程。(瀏覽器至少有一個事件循環(huán),一個事件循環(huán)至少有一個任務(wù)隊列(macrotask))
微任務(wù):
JavaScript 引擎發(fā)起的任務(wù) - JS 引擎級別
promise回調(diào),MutationObserver,process.nextTick,Object.observe
宏任務(wù)
宿主發(fā)起的任務(wù),每次的一段js代碼執(zhí)行過程,其實都是一個宏觀任務(wù) - 宿主級別
整體的js代碼,事件回調(diào),XHR回調(diào),定時器(setTimeout/setInterval/setImmediate),IO操作,UI render
宏任務(wù)和微任務(wù)關(guān)系:每個macro宏任務(wù)會維護(hù)一個micro微任務(wù)列表
事件循環(huán)過程首先我們分析有多少個宏任務(wù);
在每個宏任務(wù)中,分析有多少個微任務(wù);
根據(jù)調(diào)用次序,確定宏任務(wù)中的微任務(wù)執(zhí)行次序;
根據(jù)宏任務(wù)的觸發(fā)規(guī)則和調(diào)用次序,確定宏任務(wù)的執(zhí)行次序;
確定整個順序
視圖渲染時機(jī):本輪事件循環(huán)的microtask隊列被執(zhí)行完之后(不是每輪事件循環(huán)都會執(zhí)行視圖更新,瀏覽器有自己的優(yōu)化策略)
注意:執(zhí)行任務(wù)的耗時會影響視圖渲染的時機(jī)。通常瀏覽器以每秒60幀(60fps)的速率刷新頁面(16.7ms渲染一幀)所以如果要讓用戶覺得順暢,單個macrotask及它相關(guān)的所有microtask最好能在16.7ms內(nèi)完成。
Node 概念非阻塞 I/O 操作:盡管 JavaScript 是單線程處理的——當(dāng)有可能的時候,它們會把操作轉(zhuǎn)移到系統(tǒng)內(nèi)核中去,當(dāng)其中的一個操作完成的時候,內(nèi)核通知 Node.js 將適合的回調(diào)函數(shù)添加到 輪詢 隊列中等待時機(jī)執(zhí)行
事件循環(huán)過程
過程
event loop 的每個階段都有一個任務(wù)隊列(一個 FIFO 隊列來執(zhí)行回調(diào))
當(dāng) event loop 到達(dá)某個階段時,將執(zhí)行該階段的任務(wù)隊列,直到隊列清空或執(zhí)行的回調(diào)達(dá)到系統(tǒng)上限后,才會轉(zhuǎn)入下一個階段
當(dāng)所有階段被順序執(zhí)行一次后,稱 event loop 完成了一個 tick
每次事件循環(huán)都包含了6個階段
timers 階段:這個階段執(zhí)行timer(setTimeout、setInterval)的回調(diào)
I/O callbacks 階段:執(zhí)行一些系統(tǒng)調(diào)用錯誤,比如網(wǎng)絡(luò)通信的錯誤回調(diào)
idle, prepare 階段:僅node內(nèi)部使用
poll 階段:獲取新的I/O事件, 適當(dāng)?shù)臈l件下node將阻塞在這里
check 階段:執(zhí)行 setImmediate() 的回調(diào)
close callbacks 階段:執(zhí)行 socket 的 close 事件回調(diào)
timers 階段
Node 會去檢查有無已過期的timer,如果有則把它的回調(diào)壓入timer的任務(wù)隊列中等待執(zhí)行
技術(shù)上來說,poll 階段控制 timers 什么時候執(zhí)行。
poll 階段
poll 階段主要有2個功能:
處理 poll 隊列的事件
當(dāng)有已超時的 timer,執(zhí)行它的回調(diào)函數(shù)
執(zhí)行過程:當(dāng)event loop進(jìn)入 poll 階段,并且 沒有設(shè)定的timers(there are no timers scheduled),會發(fā)生下面兩件事之一:
如果 poll 隊列不空,event loop會遍歷隊列并同步執(zhí)行回調(diào),直到隊列清空或執(zhí)行的回調(diào)數(shù)到達(dá)系統(tǒng)上限;
如果 poll 隊列為空,則發(fā)生以下兩件事之一:
如果代碼已經(jīng)被setImmediate()設(shè)定了回調(diào), event loop將結(jié)束 poll 階段進(jìn)入 check 階段來執(zhí)行 check 隊列(里的回調(diào))。
如果代碼沒有被setImmediate()設(shè)定回調(diào),event loop將阻塞在該階段等待回調(diào)被加入 poll 隊列,并立即執(zhí)行。
當(dāng)event loop進(jìn)入 poll 階段,并且 有設(shè)定的timers,一旦 poll 隊列為空(poll 階段空閑狀態(tài)): event loop將檢查timers,如果有1個或多個timers的下限時間已經(jīng)到達(dá),event loop將繞回 timers 階段,并執(zhí)行 timer隊列。
注意:沒有setImmediate()會導(dǎo)致event loop阻塞在poll階段,這樣之前設(shè)置的timer豈不是執(zhí)行不了了?所以咧,在poll階段event loop會有一個檢查機(jī)制,檢查timer隊列是否為空,如果timer隊列非空,event loop就開始下一輪事件循環(huán),即重新進(jìn)入到timer階段。
process.nextTick() VS setImmediate()
process.nextTick()
在各個事件階段之間執(zhí)行,一旦執(zhí)行,要直到nextTick隊列被清空,才會進(jìn)入到下一個事件階段
遞歸調(diào)用 process.nextTick(),會導(dǎo)致出現(xiàn)I/O starving(饑餓)
setImmediate
對比
setTimeout(()=>{ console.log("timer1") Promise.resolve().then(function() { console.log("promise1") }) }, 0) setTimeout(()=>{ console.log("timer2") Promise.resolve().then(function() { console.log("promise2") }) }, 0) //瀏覽器: timer1 promise1 timer2 promise2 // node timer1 timer2 promise1 promise2
http://lynnelv.github.io/img/...
http://lynnelv.github.io/img/...
補(bǔ)充閱讀node單線程底層實現(xiàn)機(jī)制:
https://juejin.im/post/5b61d8...
https://yq.aliyun.com/article...
https://juejin.im/post/5b1e55...
node setTimeOut(), setInterval(), setImmediate() 以及 process.nextTick()區(qū)別
js 三種定時器的區(qū)別
https://www.cnblogs.com/onepi...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/103765.html
摘要:如果沒有其他異步任務(wù)要處理比如到期的定時器,會一直停留在這個階段,等待請求返回結(jié)果。執(zhí)行的執(zhí)行事件關(guān)閉請求的,例如事件循環(huán)的每一次循環(huán)都需要依次經(jīng)過上述的階段。因此,才會早于執(zhí)行。 showImg(https://segmentfault.com/img/bVbnY76); 概念 同步任務(wù)(Synchronous) 在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢,才能執(zhí)行后一個任務(wù) ...
摘要:單線程異步非阻塞然后,這又牽扯到了事件循環(huán)消息隊列,還有微任務(wù)宏任務(wù)這些。此步的位置不確定某個時刻后,定時器觸發(fā)線程通知事件觸發(fā)線程,事件觸發(fā)線程將回調(diào)函數(shù)加入消息隊列隊尾,等待引擎線程執(zhí)行。 前言 Philip Roberts 在演講 great talk at JSConf on the event loop 中說:要是用一句話來形容 JavaScript,我可能會這樣: Java...
摘要:主線程要明確的一點是,主線程跟執(zhí)行棧是不同概念,主線程規(guī)定現(xiàn)在執(zhí)行執(zhí)行棧中的哪個事件。主線程循環(huán)即主線程會不停的從執(zhí)行棧中讀取事件,會執(zhí)行完所有棧中的同步代碼。以上參考資料詳解中的事件循環(huán)機(jī)制中的事件循環(huán)運行機(jī)制詳解再談 showImg(https://segmentfault.com/img/remote/1460000015317437?w=1920&h=1080); 前言 大家都...
摘要:主線程不斷重復(fù)上面的三步,此過程也就是常說的事件循環(huán)。所以主線程代碼執(zhí)行時間過長,會阻塞事件循環(huán)的執(zhí)行。參考資料這一次,徹底弄懂執(zhí)行機(jī)制任務(wù)隊列的順序機(jī)制事件循環(huán)搞懂異步事件輪詢與中的事件循環(huán) 1. 說明 讀過本文章后,您能知道: JavaScript代碼在瀏覽器中的執(zhí)行機(jī)制和事件循環(huán) 面試中經(jīng)常遇到的代碼輸出順序問題 首先通過一段代碼來驗證你是否了解代碼輸出順序,如果你不知道輸出...
摘要:上代碼代碼可以看出,不僅函數(shù)比指定的回調(diào)函數(shù)先執(zhí)行,而且函數(shù)也比先執(zhí)行。這是因為后一個事件進(jìn)入的時候,事件環(huán)可能處于不同的階段導(dǎo)致結(jié)果的不確定。這是因為因為執(zhí)行完后,程序設(shè)定了和,因此階段不會被阻塞進(jìn)而進(jìn)入階段先執(zhí)行,后進(jìn)入階段執(zhí)行。 JavaScript(簡稱JS)是前端的首要研究語言,要想真正理解JavaScript就繞不開他的運行機(jī)制--Event Loop(事件環(huán)) JS是一門...
閱讀 2305·2021-09-30 09:47
閱讀 2223·2021-09-26 09:55
閱讀 2954·2021-09-24 10:27
閱讀 1543·2019-08-27 10:54
閱讀 971·2019-08-26 13:40
閱讀 2500·2019-08-26 13:24
閱讀 2423·2019-08-26 13:22
閱讀 1735·2019-08-23 18:38