javascript
javascript等待异步线程完成_作为前端你了解JavaScript运行机制吗?
作為前端工程師,大家都知道js是前端一開(kāi)始就要學(xué)會(huì)的知識(shí)點(diǎn),js的代碼你會(huì)寫(xiě)了,那js的運(yùn)行機(jī)制你了解嗎?只有了解了js的運(yùn)行機(jī)制,才能在工作中如魚(yú)得水,今天就跟隨珠峰的老師一起來(lái)了解下js的運(yùn)行機(jī)制吧。
JavaScript單線(xiàn)程模型JavaScript是單線(xiàn)程的,JavaScript只在一個(gè)線(xiàn)程上運(yùn)行,但是瀏覽器是多線(xiàn)程的,典型的瀏覽器有如下線(xiàn)程:
JavaScript引擎線(xiàn)程
GUI渲染線(xiàn)程
瀏覽器事件觸發(fā)線(xiàn)程
瀏覽器Http請(qǐng)求線(xiàn)程
JavaScript之所以采用單線(xiàn)程 而不是多線(xiàn)程,由于作為瀏覽器腳本語(yǔ)言,主要用途是與用戶(hù)互動(dòng),以及操作DOM(文檔對(duì)象模型)和BOM(瀏覽器對(duì)象模型), 而多線(xiàn)程需要共享資源,多線(xiàn)程編程經(jīng)?常面臨鎖、狀態(tài)同步等問(wèn)題。
假定JavaScript同時(shí)有兩個(gè)線(xiàn)程,這兩個(gè)線(xiàn)程同時(shí)操作同一個(gè)DOM增刪修改操作,這時(shí)瀏覽器應(yīng)該以哪個(gè)線(xiàn)程操作為準(zhǔn)?無(wú)疑會(huì)帶來(lái)同步問(wèn)題。
既然JavaScript是單線(xiàn)程的,這就意味著,一次只能運(yùn)行一個(gè)任務(wù),其他任務(wù)都必須在后面排隊(duì)等待。
為了利用多核CPU的計(jì)算能力,HTML5提出了Web Worker,它會(huì)在當(dāng) ? 前JavaScript的執(zhí)行主線(xiàn)程中利用Worker類(lèi)新開(kāi)辟一個(gè)額外的線(xiàn)程來(lái)加載和運(yùn)行特定的JavaScript文件,但在HTML5 Web Worker中是不能操作DOM的,任何需要操作DOM的任務(wù)都需要委托給JavaScript主線(xiàn)程來(lái)執(zhí)行,所以雖然引入HTML5 Web Worker,但仍然沒(méi)有改變JavaScript單線(xiàn)程的本質(zhì)。
Javascript有一個(gè)main thread 主進(jìn)程和call-stack(一個(gè)調(diào)用堆棧),在對(duì)一個(gè)調(diào)用堆棧中的task處理的時(shí)候,其他的都要等著。當(dāng)在執(zhí)行過(guò)程中遇到一些類(lèi)似于setTimeout等異步操作的時(shí)候,會(huì)交給瀏覽器的其他模塊(以webkit為例,是webcore模塊)進(jìn)行處理,當(dāng)?shù)竭_(dá)setTimeout指定的延時(shí)執(zhí)行的時(shí)間之后,task(回調(diào)函數(shù))會(huì)放入到任務(wù)隊(duì)列之中。
一般不同的異步任務(wù)的回調(diào)函數(shù)會(huì)放入不同的任務(wù)隊(duì)列之中。等到調(diào)用棧中所有task執(zhí)行完畢之后,接著去執(zhí)行任務(wù)隊(duì)列之中的task(回調(diào)函數(shù))。
異步和同步
一般而言,操作分為:發(fā)出調(diào)用和得到結(jié)果兩步。
同步:
同步是指,發(fā)出調(diào)用,但無(wú)法立即得到結(jié)果,需要一直等待,直到返回結(jié)果。同步任務(wù)會(huì)進(jìn)入主線(xiàn)程, 主線(xiàn)程后面任務(wù)必須要等當(dāng)前任務(wù)執(zhí)行完才能執(zhí)行,從而導(dǎo)致主線(xiàn)程阻塞。
異步:
異步是指,調(diào)用之后,不能直接拿到結(jié)果,通過(guò)event loop事件處理機(jī)制,在Event Queue注冊(cè)回調(diào)函數(shù)最終拿到結(jié)果(拿到結(jié)果中間的時(shí)間可以介入其他任務(wù))。
JavaScript是如何工作的,首先理解幾個(gè)概念JS Engine(JS引擎)
Runtime(運(yùn)行上下文)
Call Stack(調(diào)用棧)
Event Loop(事件循環(huán))
Callback(回調(diào))
JS Engine
JavaScript引擎就是用來(lái)執(zhí)行JS代碼的, 通過(guò)編譯器將代碼編譯成可執(zhí)行的機(jī)器碼讓計(jì)算機(jī)去執(zhí)行(Java中的JVM虛擬機(jī)一樣)。
常見(jiàn)的JavaScript虛擬機(jī)(一般也把虛擬機(jī)稱(chēng)為引擎):
Chakra(Microsoft Internet Explorer)
Nitro/JavaScript Core (Safari)
Carakan (Opera)
SpiderMonkey (Firefox)
V8 (Chrome, Chromium)
目前比較流行的就是V8引擎,Chrome瀏覽器和Node.js采用的引擎就是V8引擎。
引擎主要由堆(Memory Heap)和棧(Call Stack)組成
Heap(堆) - JS引擎中給對(duì)象分配的內(nèi)存空間是放在堆中的
Stack(棧)- 這里存儲(chǔ)著JavaScript正在執(zhí)行的任務(wù)。每個(gè)任務(wù)被稱(chēng)為幀(stack of frames)。
主線(xiàn)程運(yùn)行的時(shí)候,產(chǎn)生堆(heap)和棧(stack),棧中的代碼調(diào)用個(gè)各種外部api。
RunTime (運(yùn)行環(huán)境)
JS在瀏覽器環(huán)境中運(yùn)行時(shí),BOM和DOM對(duì)象提供了很多相關(guān)外部接口(這些接口不是V8引擎提供的),供JS運(yùn)行時(shí)調(diào)用,以及JS的事件循環(huán)(Event Loop)和事件隊(duì)列(Callback Queue),把這些稱(chēng)為RunTime。在Node.js中,可以把Node的各種庫(kù)提供的API稱(chēng)為RunTime
Call Stack
當(dāng)JavaScript代碼執(zhí)行的時(shí)候,創(chuàng)建執(zhí)行環(huán)境是很重要的,它可能是下面三種情況中的一種:
全局 code(Global code)——代碼第一次執(zhí)行的默認(rèn)環(huán)境
函數(shù) code(Function code)——執(zhí)行流進(jìn)入函數(shù)體
Eval code(Eval code)——代碼在eval函數(shù)內(nèi)部執(zhí)行
JavaScript代碼首次被載入時(shí),會(huì)創(chuàng)建一個(gè)全局上下文,當(dāng)調(diào)用一個(gè)函數(shù)時(shí),會(huì)創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文。
在計(jì)算機(jī)系統(tǒng)中棧是一種遵從先進(jìn)后出(FILO)原則的區(qū)域。函數(shù)被調(diào)用時(shí),創(chuàng)建一個(gè)新的執(zhí)行環(huán)境,就會(huì)被加入到執(zhí)行棧頂部,瀏覽器始終執(zhí)行當(dāng)前在棧頂部的執(zhí)行環(huán)境。一旦函數(shù)完成了當(dāng)前的執(zhí)行環(huán)境,它就會(huì)被彈出棧的頂部, 把控制權(quán)返回給當(dāng)前執(zhí)行環(huán)境的下個(gè)執(zhí)行環(huán)境。
案例:瀏覽器第一次加載你的script,它默認(rèn)的進(jìn)了全局執(zhí)行環(huán)境,然后main執(zhí)行創(chuàng)建一個(gè)新的執(zhí)行環(huán)境,把它添加到已經(jīng)存在的執(zhí)行棧的頂部,在里面執(zhí)行Student構(gòu)造函數(shù),執(zhí)行流進(jìn)入內(nèi)部函數(shù) 將生成執(zhí)行環(huán)境添加到當(dāng)前棧頂,在Student構(gòu)造函數(shù)里,又調(diào)用sayHi方法,再次把sayHi生成執(zhí)行環(huán)境壓入到棧頂。當(dāng)函數(shù)執(zhí)行完一次彈出棧頂。
class?Student?{constructor(age,?name)?{this.name?=?name;this.age?=?age;this.sayName();?//?stack?3????}
????sayName()?{console.log(`my?name?is?${this.name},?this?year?age?is?${this.age}`);
????}
}function?main(age,?name)?{new?Student(age,?name);?//?stack?2
}
main(23,?'John');?//?stack?1
程序運(yùn)行時(shí),首先main()函數(shù)的執(zhí)行上下文入棧,再調(diào)用Student構(gòu)造函數(shù)添加到當(dāng)前棧尾,在Student里再調(diào)用sayName()方法,添加到此時(shí)棧尾。最終main方法所在的位置叫棧底,sayName方法所在的位置是棧頂,層層調(diào)用,直至整個(gè)調(diào)用棧完成返回結(jié)果,最后再由棧頂依次出棧。
Event Loop & Callback
Event Loop 類(lèi)似于一個(gè)while(true)的循環(huán),每執(zhí)行一次循環(huán)體的過(guò)程我們成為T(mén)ick。每個(gè)Tick的過(guò)程就是查看是否有事件待處理,當(dāng)Call Stack里面的調(diào)用棧運(yùn)行完變成空了,就取出事件及其相關(guān)的回調(diào)函數(shù)。放到調(diào)用棧中并執(zhí)行它。
調(diào)用棧中遇到DOM操作、ajax請(qǐng)求以及setTimeout等WebAPIs的時(shí)候就會(huì)交給瀏覽器內(nèi)核的其他模塊進(jìn)行處理,webkit內(nèi)核在Javasctipt執(zhí)行引擎之外,有一個(gè)重要的模塊是webcore模塊。對(duì)于圖中WebAPIs提到的三種API,webcore分別提供了DOM Binding、network、timer模塊來(lái)處理底層實(shí)現(xiàn)。
等到這些模塊處理完這些操作的時(shí)候?qū)⒒卣{(diào)函數(shù)放入任務(wù)隊(duì)列中,之后等棧中的task執(zhí)行完之后再去執(zhí)行任務(wù)隊(duì)列之中的回調(diào)函數(shù)。
Javascript有一個(gè)main thread 主進(jìn)程和call-stack(一個(gè)調(diào)用堆棧),在對(duì)一個(gè)調(diào)用堆棧中的task處理的時(shí)候,其他的都要等著。當(dāng)在執(zhí)行過(guò)程中遇到一些類(lèi)似于setTimeout等異步操作的時(shí)候,會(huì)交給瀏覽器的其他模塊(以webkit為例,是webcore模塊)進(jìn)行處理,當(dāng)?shù)竭_(dá)setTimeout指定的延時(shí)執(zhí)行的時(shí)間之后,task(回調(diào)函數(shù))會(huì)放入到任務(wù)隊(duì)列之中。
一般不同的異步任務(wù)的回調(diào)函數(shù)會(huì)放入不同的任務(wù)隊(duì)列之中。等到調(diào)用棧中所有task執(zhí)行完畢之后,接著去執(zhí)行任務(wù)隊(duì)列之中的task(回調(diào)函數(shù))。
代碼案例:console.log('Hi');setTimeout(function?cb1()?{console.log('cb1');
},?5000);console.log('Bye');
以上代碼從上到下 首先執(zhí)行l(wèi)og('Hi') 它是一個(gè)普通方法立即被執(zhí)行,當(dāng)遇到定時(shí)器的時(shí)候,執(zhí)行引擎將其添加到調(diào)用棧,調(diào)用棧發(fā)現(xiàn)setTimeout是WebAPIs中的API,將其出棧交給瀏覽器的timer模塊進(jìn)行處理,此時(shí)timer模塊去處理延遲執(zhí)行的函數(shù),此時(shí)執(zhí)行l(wèi)og('Bye'),輸出'Bye',當(dāng)timer模塊中延時(shí)方法規(guī)定的時(shí)間到了之后就將其放入到任務(wù)隊(duì)列之中,此時(shí)調(diào)用棧中的task已經(jīng)全部執(zhí)行完畢。
調(diào)用棧中的task執(zhí)行完畢之后,執(zhí)行引擎會(huì)接著看執(zhí)行任務(wù)隊(duì)列中是否有需要執(zhí)行的回調(diào)函數(shù)。
Event Loop處理機(jī)制什么是Event Loop?
Event Loop(事件循環(huán))是實(shí)現(xiàn)異步的一種機(jī)制,允許 Node.js 執(zhí)行非阻塞 I/O 操作 。
大多數(shù)現(xiàn)代的系統(tǒng)內(nèi)核都是多線(xiàn)程的, 他們?cè)诤笈_(tái)可以處理多個(gè)同時(shí)執(zhí)行的操作. 當(dāng)其中一個(gè)操作完成時(shí), 系統(tǒng)內(nèi)核會(huì)通知Node.js, 然后與之相關(guān)的回調(diào)函數(shù)會(huì)被加入到 poll隊(duì)列 并且最終被執(zhí)行。
注意: 在Windows和Unix/Linux實(shí)現(xiàn)之間存在一點(diǎn)小小的差異, 但對(duì)本示例來(lái)說(shuō)這并不重要,最重要的部分都已列在這里了,實(shí)際上有7或8個(gè)階段, 但我們關(guān)心的和Node.js實(shí)際會(huì)用到的階段都已經(jīng)列在了上面。
每個(gè)階段都有一個(gè)先進(jìn)先出(FIFO)的隊(duì)列,里面存放著要執(zhí)行的回調(diào)函數(shù),然而每個(gè)階段都有其特殊之處,當(dāng)事件循環(huán)進(jìn)入了某個(gè)階段后,它可以執(zhí)行該階段特有的任意操作,然后進(jìn)行該階段的任務(wù)隊(duì)列中的回調(diào)函數(shù),一直到隊(duì)列為空或已執(zhí)行回調(diào)的數(shù)量達(dá)到了允許的最大值,當(dāng)隊(duì)列為空或已執(zhí)行回調(diào)的數(shù)量達(dá)到了允許的最大值時(shí),事件循環(huán)會(huì)進(jìn)入下一個(gè)階段,階段之間會(huì)互相轉(zhuǎn)換,循環(huán)順序并不是完全固定的 ,因?yàn)楹芏嚯A段是由外部的事件觸發(fā)的。
階段概覽
timers(定時(shí)器):此階段執(zhí)行由setTimeout()和setInterval() 調(diào)度的回調(diào)函數(shù)
I/O callbacks(I/O回調(diào)): 此階段會(huì)執(zhí)行幾乎所有的回調(diào)函數(shù), 除了 close callbacks(關(guān)閉回調(diào)) 和 那些由 timers 與 setImmediate() 調(diào)度的回調(diào).
idle(空閑),prepare(預(yù)備): 此階段只在內(nèi)部調(diào)用
poll(輪詢(xún)): 檢索新的I/O事件,在恰當(dāng)?shù)臅r(shí)候會(huì)阻塞在這個(gè)階段
check(檢查): setImmediate() 設(shè)置的回調(diào)會(huì)在此階段被調(diào)用
close callbacks(關(guān)閉事件的回調(diào)): 諸如 socket.on('close', ...) 此類(lèi)的回調(diào)在此階段被調(diào)用
在事件循環(huán)的每次運(yùn)行之間,Node.js會(huì)檢查它是否在等待異步I/O或定時(shí)器, 如果沒(méi)有的話(huà)就會(huì)自動(dòng)關(guān)閉。
一次事件循環(huán)就是處理以上幾個(gè)phase的過(guò)程,此外還有兩個(gè)比較特殊的隊(duì)列Next Ticks Queue和Other Microtasks Queue,那另外兩個(gè)特殊的隊(duì)列是在什么時(shí)候運(yùn)行的呢?
答案: 就是在每個(gè) phase運(yùn)行完后馬上就檢查這兩個(gè)隊(duì)列有無(wú)數(shù)據(jù),有的話(huà)就馬上執(zhí)行這兩個(gè)隊(duì)列中的數(shù)據(jù)直至隊(duì)列為空。當(dāng)這兩個(gè)隊(duì)列都為空時(shí),event loop 就會(huì)接著執(zhí)行下一個(gè)phase。
這兩個(gè)隊(duì)列相比,Next Ticks Queue的權(quán)限要比Other Microtasks Queue的權(quán)限要高,因此Next Ticks Queue會(huì)先執(zhí)行。
兩個(gè)比較特殊的隊(duì)列:
Next Ticks Queue: 保存process.nextTick中的回調(diào)函數(shù)
Other Microtasks Queue: 保存promise等microtask中的回調(diào)函數(shù)。
階段詳情
由于這些操作中的任意一個(gè)都可以調(diào)度更多的操作, 在 poll(輪詢(xún)) 階段處理的新事件被系統(tǒng)內(nèi)核加入隊(duì)列, 當(dāng)輪詢(xún)事件正在被處理時(shí)新的輪詢(xún)事件也可以被加入隊(duì)列. 因此, 長(zhǎng)時(shí)間運(yùn)行的回調(diào)函數(shù)可以讓 poll 階段運(yùn)行的時(shí)間比 timer(計(jì)時(shí)器) 的閾值長(zhǎng)得多。 看下面timer 和 poll 部分了解更多細(xì)節(jié)。
timers
給一個(gè)定時(shí)器(setTimeout/setInterval)指定時(shí)間閾值時(shí),給定的回調(diào)函數(shù)有時(shí)并不是在精確的時(shí)間閾值點(diǎn)執(zhí)行,定時(shí)器的閾值只是說(shuō) 至少在這個(gè)時(shí)間閾值點(diǎn)執(zhí)行,然而操作系統(tǒng)調(diào)度或其他回調(diào)的執(zhí)行可能會(huì)延遲定時(shí)器回調(diào)的執(zhí)行。
注意:從技術(shù)來(lái)講, poll階段會(huì)控制定時(shí)器何時(shí)被執(zhí)行
const?fs?=?require('fs');//?設(shè)定一個(gè)100ms執(zhí)行的定時(shí)器const?startTime?=?Date.now();setTimeout(()?=>?{console.log('timeout延遲執(zhí)行時(shí)間',?Date.now()?-?startTime);console.log('timer');
},?100);//?異步讀取文件?假設(shè)95ms完成讀取任務(wù)
fs.readFile('./1.txt',?(err,?data)?=>?{?//?回調(diào)函數(shù)中又耗費(fèi)100毫秒const?startTime?=?Date.now();while?(Date.now()?-?startTime?200)?{
????????//?console.log(Date.now()?-?startTime);
????}
});
開(kāi)始事件循環(huán)定時(shí)器被加入到timer中延遲執(zhí)行,當(dāng)事件循環(huán)進(jìn)入poll階段,它有一個(gè)隊(duì)列執(zhí)行I/O操作(fs.readFile())還未完成,poll階段將會(huì)阻塞,大約95ms 完成了I/O操作(文件讀取),將要耗時(shí)10ms才能完成的回調(diào)加入poll隊(duì)列并執(zhí)行,當(dāng)回調(diào)執(zhí)行完成,poll Queue為空,此時(shí)poll會(huì)去timer階段查看最近有沒(méi)有到期的定時(shí)器,發(fā)現(xiàn)存在一個(gè)已經(jīng)超時(shí)將近195ms的定時(shí)器,并執(zhí)行定時(shí)器回調(diào)。在這個(gè)例子中如果不假設(shè)讀取時(shí)間,定時(shí)器執(zhí)行的時(shí)間間隔大約為200ms。
注意: 為了防止 poll 階段阻塞事件循環(huán), libuv(一個(gè)實(shí)現(xiàn)了Node.js事件循環(huán)和Node.js平臺(tái)所有異步行為的C語(yǔ)言庫(kù)), 有一個(gè)嚴(yán)格的最大限制(這個(gè)值取決于操作系統(tǒng)), 在超過(guò)此限制后就會(huì)停止輪詢(xún).
I/O callbacks
此階段執(zhí)行一些系統(tǒng)操作處理 I/O 異常錯(cuò)誤;,如TCP的errors回調(diào)函數(shù)。
poll
poll 階段主要有兩個(gè)功能:
1.執(zhí)行時(shí)間閾值已過(guò)去的定時(shí)器回調(diào)
2.處理poll隊(duì)列中的事件
當(dāng)事件循環(huán)進(jìn)入poll階段并且 當(dāng)前沒(méi)有定時(shí)器時(shí),以下兩種情況其中一種會(huì)發(fā)生:
如果poll隊(duì)列不是空的,事件循環(huán)會(huì)遍歷隊(duì)列并同步執(zhí)行里面的回調(diào)函數(shù),直到隊(duì)列為空或者到達(dá)操作系統(tǒng)的限制(操作系統(tǒng)規(guī)定的連續(xù)調(diào)用回調(diào)函數(shù)的數(shù)量的最大值)
如果poll隊(duì)列是空的,則以下兩種情況其中一種將發(fā)生:
如果存在被 setImmediate() 調(diào)度的回調(diào),事件循環(huán)會(huì)結(jié)束poll階段并進(jìn)入check階段執(zhí)行那些被 setImmediate() 調(diào)度了的回調(diào)。
如果沒(méi)有任何被 setImmediate() 調(diào)度的回調(diào),事件循環(huán)會(huì)等待回調(diào)函數(shù)被加入隊(duì)列,一旦回調(diào)函數(shù)加入了隊(duì)列,就立即執(zhí)行它們。
一旦poll隊(duì)列變?yōu)榭?#xff0c;事件循環(huán)就檢查是否已經(jīng)存在超時(shí)的定時(shí)器,如果存在,事件循環(huán)將繞回到timers階段執(zhí)行這些定時(shí)器回調(diào)。
check
此階段如果poll階段變?yōu)榭辙D(zhuǎn)(idle)狀態(tài),如果存在被 setImmediate() 調(diào)度的回調(diào),事件循環(huán)不會(huì)在poll階段阻塞等待相應(yīng)的I/O事件,而直接去check階段執(zhí)行 setImmediate() 函數(shù)。
close callbacks
如果一個(gè)socket或句柄被突然關(guān)閉(例如 socket.destroy()), 'close'事件會(huì)在此階段被觸發(fā). 否則 'close'事件會(huì)通過(guò) process.nextTick() 被觸發(fā).
setImmediate() vs setTimeout()
setImmediate() 被設(shè)計(jì)為: 一旦當(dāng)前的poll階段完成就執(zhí)行回調(diào)
setTimeout() 調(diào)度一個(gè)回調(diào)在時(shí)間閥值之后被執(zhí)行
這兩種定時(shí)器的執(zhí)行順序可能會(huì)變化, 這取決于他們是在哪個(gè)上下文中被調(diào)用的. 如果兩種定時(shí)器都是從主模塊內(nèi)被調(diào)用的, 那么回調(diào)執(zhí)行的時(shí)機(jī)就受進(jìn)程性能的約束(進(jìn)程也會(huì)受到系統(tǒng)中正在運(yùn)行的其他應(yīng)用程序的影響).
setTimeout(function?timeout()?{console.log('timeout');},?0);
setImmediate(function?immediate()?{console.log('immediate');
});
但如果把setImmediate和setTimeout放到了I/O周期中,此時(shí)他們的執(zhí)行順序永遠(yuǎn)都是immediate在前,timeout在后。
const?fs?=?require('fs');fs.readFile(__filename,?()?=>?{
??setTimeout(()?=>?{console.log('timeout');
??},?0);
??setImmediate(()?=>?{console.log('immediate');
??});
});
相比于 setTimeout(), 使用 setImmediate() 的主要優(yōu)點(diǎn)在于: 只要時(shí)在I/O周期內(nèi), 不管已經(jīng)存在多少個(gè)定時(shí)器, setImmediate()設(shè)置的回調(diào)總是在定時(shí)器回調(diào)之前執(zhí)行。
process.nextTick()
在上面我們提到了Next Ticks Queue特殊的隊(duì)列,在這個(gè)隊(duì)列里主要存放process.nextTick這個(gè)異步函數(shù)。從技術(shù)上講該階段并不屬于事件循環(huán)的一部分,不管當(dāng)前事件循環(huán)處于哪個(gè)階段,只要當(dāng)前階段操作完畢后進(jìn)入下個(gè)階段前瞬間執(zhí)行process.nextTick()。
這樣一來(lái)任何時(shí)候在給定階段調(diào)用process.nextTick()時(shí),所有傳入process.nextTick()的回調(diào)都會(huì)在事件循環(huán)繼續(xù)之前被執(zhí)行。由于允許開(kāi)發(fā)者通過(guò)遞歸調(diào)用 process.nextTick() 來(lái)阻塞I/O操作, 這也使事件循環(huán)無(wú)法到達(dá) poll 階段.
利用process.nextTick函數(shù),我們可以對(duì)內(nèi)部函數(shù)作異步處理可能出現(xiàn)的異常,porcess.nextTick(callback, ...args) 允許接收多個(gè)參數(shù),callback后面的參數(shù)會(huì)作為callback的實(shí)參傳遞進(jìn)來(lái),這樣就無(wú)需嵌套函數(shù)了。
function?apiCall(arg,?callback)?{if?(typeof?arg?!==?'string')return?process.nextTick(callback,new?TypeError('argument?should?be?string'));????callback.call(this,?arg);
};
apiCall(1,?(err)?=>?{console.log(err);
});
apiCall('node',?(err)?=>?{console.log(err);
});
setTimeout() setImmediate() process.nextTick()
setTimeout() 在某個(gè)時(shí)間值過(guò)后盡快執(zhí)行回調(diào)函數(shù);
process.nextTick() 在當(dāng)前調(diào)用棧結(jié)束后就立即處理,這時(shí)也必然是“事件循環(huán)繼續(xù)進(jìn)行之前”
setImmediate() 函數(shù)是在poll階段完成后進(jìn)去check階段時(shí)執(zhí)行
優(yōu)先級(jí)順序從高到低: process.nextTick() > setImmediate() > setTimeout()注:這里只是多數(shù)情況下,即輪詢(xún)階段(I/O 回調(diào)中)。比如之前比較 setImmediate() 和 setTimeout() 的時(shí)候就區(qū)分了所處階段/上下文。
Macrotask Queue和Microtask Queue
macrotask 和 microtask 這兩個(gè)概念, 表示異步任務(wù)的兩種分類(lèi)。在掛起任務(wù)時(shí),JS 引擎會(huì)將所有任務(wù)按照類(lèi)別分到這兩個(gè)隊(duì)列中,首先在 macrotask 的隊(duì)列(這個(gè)隊(duì)列也被叫做 task queue)中取出第一個(gè)任務(wù),執(zhí)行完畢后取出 microtask 隊(duì)列中的所有任務(wù)順序執(zhí)行;之后再取 macrotask 任務(wù),周而復(fù)始,直至兩個(gè)隊(duì)列的任務(wù)都取完。
macrotask(宏任務(wù)、大任務(wù)):
script(整體代碼)
setTimeout
setInterval
setImmediate
I/O
UI rendering
microtask(微任務(wù)、小任務(wù)):
promise
Object.observe
process.nextTick
MutationObserver
每個(gè)事件循環(huán)只處理一個(gè)macrotask(大任務(wù)) ,但會(huì)處理完所有microtask(小任務(wù))。
參考資料
JS運(yùn)行機(jī)制
Node.JS事件循環(huán)
Javascript事件循環(huán)機(jī)制
事件循環(huán)
珠峰前端課程開(kāi)課時(shí)間
2019年2月11日?《零基礎(chǔ)入門(mén)課程》?2019年2月11日?《全日制框架課程》
2019年2月11日?《周末班框架課程》?
2019年2月11日?《前端就業(yè)課程》
2019年2月13日?《前端架構(gòu)課程》
總結(jié)
以上是生活随笔為你收集整理的javascript等待异步线程完成_作为前端你了解JavaScript运行机制吗?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python办公代码_[Python]
- 下一篇: 文件表单带数据一起提交spring_基于