定时器和promise_从Promise链理解EventLoop
面試題
new Promise(resolve => { setTimeout(()=>{ console.log(666); new Promise(resolve => { resolve(); }) .then(() => {console.log(777);}) }) resolve(); }) .then(() => { new Promise(resolve => { resolve(); }) .then(() => {console.log(111);}) .then(() => {console.log(222);}); }) .then(() => { new Promise((resolve) => { resolve() }) .then(() => { new Promise((resolve) => { resolve() }) .then(() => {console.log(444)}) }) .then(() => { console.log(555); })}).then(() => { console.log(333);})答案
111222333444555666777如果你沒(méi)有得出正確的結(jié)果,有必要繼續(xù)往下看.
為了能正確解答上題,需要對(duì)宏任務(wù)、微任務(wù)以及Event-Loop深入理解.
知識(shí)點(diǎn)
宏任務(wù)
瀏覽器執(zhí)行代碼的過(guò)程中,JS引擎會(huì)將大部分代碼進(jìn)行分類,分別分到這兩個(gè)隊(duì)列中--宏任務(wù)(macrotask ) 和 微任務(wù)(microtask ) .
常見的宏任務(wù):script(整體代碼), XHR回調(diào),setTimeout, setInterval, setImmediate(node獨(dú)有), I/O.
上面的描述仍然有些生澀,下面借助案例深入理解.
app.js
setTimeout(()=>{ //宏任務(wù)2 console.log(2); },0) setTimeout(()=>{ //宏任務(wù)3 console.log(3); },0) console.log(1);執(zhí)行結(jié)果: 1 -- 2 -- 3
?瀏覽器開始運(yùn)行 app.js 時(shí)啟動(dòng)了第一個(gè)宏任務(wù)(宏任務(wù)1,指向app.js整體代碼)并開始執(zhí)行.?在執(zhí)行宏任務(wù)1途中遇到了第一個(gè)定時(shí)器,瀏覽器便會(huì)開啟一個(gè)新的宏任務(wù)2,定時(shí)器被添加到宏任務(wù)隊(duì)列等待,線程繼續(xù)往下執(zhí)行.?隨后又遇到了定時(shí)器開啟一個(gè)新的宏任務(wù)3,定時(shí)器又被添加到宏任務(wù)隊(duì)列等待,宏任務(wù)3排在宏任務(wù)2的后面,線程繼續(xù)往下執(zhí)行.?線程走到最后輸出了1,此時(shí)宏任務(wù)1就結(jié)束了.瀏覽器此刻就會(huì)去宏任務(wù)隊(duì)列中尋找,排在最前面的是宏任務(wù)2,發(fā)現(xiàn)延遲時(shí)間已到允許執(zhí)行便輸出了2,宏任務(wù)2結(jié)束又執(zhí)行宏任務(wù)3輸出3.
宏任務(wù)通常是由宿主環(huán)境開啟.比如在客戶端,瀏覽器就是宿主環(huán)境.開始執(zhí)行一個(gè)腳本文件,開啟一個(gè)定時(shí)器任務(wù)以及ajax請(qǐng)求,都是瀏覽器在其底層完成,并非是通過(guò)js 引擎去做的這些工作.在服務(wù)器端,node就作為了宿主環(huán)境.
微任務(wù)
微任務(wù)是宏任務(wù)的組成部分,微任務(wù)與宏任務(wù)是包含關(guān)系,并非前后并列.如果要談微任務(wù),需要指出它屬于哪個(gè)宏任務(wù)才有意義.
常見的宏任務(wù):process.nextTick(nodejs端),Promise等.
app.js
console.log(1); new Promise((resolve)=>{ resolve(); }).then(()=>{ console.log(2) }) console.log(3)執(zhí)行結(jié)果: 1 -- 3 -- 2
?運(yùn)行 app.js 腳本文件啟動(dòng)宏任務(wù)1,第一行代碼執(zhí)行輸出1.?碰到Promise,將then的回調(diào)函數(shù)放入宏任務(wù)1的微任務(wù)隊(duì)列中等待,線程繼續(xù)往下.?代碼跑到最后一行輸出3.此時(shí)同步代碼執(zhí)行完畢,開始檢查當(dāng)前宏任務(wù)中的微任務(wù)隊(duì)列.?運(yùn)行微任務(wù)隊(duì)列中的第一個(gè)then回調(diào)函數(shù)輸出2.再檢查微任務(wù)隊(duì)列,沒(méi)有發(fā)現(xiàn)其他任務(wù).?微任務(wù)隊(duì)列執(zhí)行完畢,宏任務(wù)1執(zhí)行完畢.
宏任務(wù)由宿主環(huán)境開啟,與此相對(duì)應(yīng),微任務(wù)是 js 引擎從代碼層面開啟的.
如果還對(duì)宏任務(wù)和微任務(wù)的關(guān)系模棱兩可,下面從 Event-Loop 角度詳細(xì)闡述.
Event-Loop
Event-Loop從上圖可知,宏任務(wù)形成了一個(gè)擁有先后順序的隊(duì)列.每個(gè)宏任務(wù)中分為同步代碼和微任務(wù)隊(duì)列.
?假設(shè)js當(dāng)前的線程執(zhí)行宏任務(wù)1,先執(zhí)行宏任務(wù)1中的同步代碼.?如果碰到Promise或者process.nextTick,就把它們的回調(diào)放入當(dāng)前宏任務(wù)1的微任務(wù)隊(duì)列中.?如果碰到setTimeout, setInterval之類就會(huì)在當(dāng)前宏任務(wù)1的隊(duì)列后面開啟新的宏任務(wù)將回調(diào)放入其中.?同步代碼執(zhí)行完,開始執(zhí)行宏任務(wù)1的微任務(wù)隊(duì)列,直到微任務(wù)隊(duì)列的所有任務(wù)都執(zhí)行完.?微任務(wù)隊(duì)列的所有任務(wù)執(zhí)行完畢,宏任務(wù)1再看沒(méi)有其他代碼了,當(dāng)前的事件循環(huán)結(jié)束.js線程開始執(zhí)行下一個(gè)宏任務(wù),直到所有宏任務(wù)執(zhí)行完畢.如此整體便構(gòu)成了事件循環(huán)機(jī)制.
延伸
dom操作屬于宏任務(wù)還是微任務(wù)
console.log(1); document.getElementById("div").style.color = "red"; console.log(2);在實(shí)踐中發(fā)現(xiàn),當(dāng)上面代碼執(zhí)行到第三行時(shí),控制臺(tái)輸出了1并且頁(yè)面已經(jīng)完成了重繪,div的顏色變成了紅色.
dom操作它既不是宏任務(wù)也不是微任務(wù),它應(yīng)該歸于同步執(zhí)行的范疇.
requestAnimationFrame屬于宏任務(wù)還是微任務(wù)
setTimeout(() => { console.log("11111")}, 0)requestAnimationFrame(() => { console.log("22222")})new Promise(resolve => { console.log('promise'); resolve();}).then(() => {console.log('then')})執(zhí)行結(jié)果: promise -- then -- 22222 -- 11111
很多人會(huì)把 requestAnimationFrame 歸結(jié)到宏任務(wù)中,因?yàn)榘l(fā)現(xiàn)它會(huì)在微任務(wù)隊(duì)列完成后執(zhí)行.
但實(shí)際上 requestAnimationFrame 它既不能算宏任務(wù),也并非是微任務(wù).它的執(zhí)行時(shí)機(jī)是在當(dāng)前宏任務(wù)范圍內(nèi),執(zhí)行完同步代碼和微任務(wù)隊(duì)列后再執(zhí)行.它仍然屬于宏任務(wù)范圍內(nèi),但是是在微任務(wù)隊(duì)列執(zhí)行完畢后才執(zhí)行.
Promise的運(yùn)行機(jī)制
包裹函數(shù)是同步代碼
new Promise((resolve)=>{ console.log(1); resolve(); }).then(()=>{ console.log(2); })new Promise里面的包裹的函數(shù),也就是輸出1的那段代碼是同步執(zhí)行的.而then包裹的函數(shù)才會(huì)被加載到微任務(wù)隊(duì)列中等待執(zhí)行.
Promise鏈條如果沒(méi)有return
new Promise((resolve)=>{ console.log(1) resolve();}).then(()=>{ console.log(2);}).then(()=>{ console.log(3);}).then(()=>{ console.log(4);})執(zhí)行結(jié)果: 1 -- 2 -- 3 -- 4
在平時(shí)開發(fā)中,在Promise鏈中通常會(huì)返回一個(gè)新的Promise做異步操作返回相應(yīng)的值.如下.
new Promise((resolve)=>{ console.log(1) resolve();}).then(()=>{ return new Promise((resolve)=>{ resolve(2) })}).then((n)=>{ console.log(n);})執(zhí)行結(jié)果: 1 -- 2
但上述代碼中,then函數(shù)的回調(diào)里沒(méi)有返回任何東西.但是后續(xù)then包含的回調(diào)函數(shù)仍然會(huì)依次執(zhí)行,返回 1 -- 2 -- 3 -- 4.并且它可以在末尾無(wú)限接then函數(shù),這些函數(shù)也都會(huì)依次執(zhí)行.
多個(gè)then函數(shù)執(zhí)行次序
new Promise((resolve)=>{ // 1 console.log("a") // 2 resolve(); // 3}).then(()=>{ // 4 console.log("b"); // 5}).then(()=>{ // 6 console.log("c"); // 7}) // 8console.log("d") // 9執(zhí)行結(jié)果: a -- d -- b -- c
?1,2,3行為同步執(zhí)行的代碼,一氣呵成輸出 a.?此時(shí)線程走到第4行碰到then函數(shù)的回調(diào),將其放入微任務(wù)的隊(duì)列等待.?線程繼續(xù)往后走直接跳到了第9行輸出了 d,為什么會(huì)忽略第6行的then直接跳到第9行呢?因?yàn)榈?行的then函數(shù)回調(diào)執(zhí)行完畢后才會(huì)開始執(zhí)行第6行的代碼.(如果不理解為什么此刻會(huì)忽略掉第6行代碼可以查閱一下函數(shù)柯里化的概念).?同步代碼執(zhí)行完畢,開始執(zhí)行微任務(wù)隊(duì)列.此時(shí)微任務(wù)隊(duì)列里面只包含了一個(gè)then的回調(diào)函數(shù),執(zhí)行輸出b.?4,5行執(zhí)行完畢后,開始執(zhí)行第6行代碼.發(fā)現(xiàn)了then函數(shù)回調(diào),將其放入微任務(wù)隊(duì)列中.此時(shí)第一個(gè)微任務(wù)執(zhí)行完了,將其清空.?微任務(wù)隊(duì)列中還有一個(gè)剛放進(jìn)去的微任務(wù),執(zhí)行輸出 c.清除此微任務(wù),至此微任務(wù)隊(duì)列為空,全部任務(wù)執(zhí)行完畢.
解題
有了以上知識(shí)的儲(chǔ)備再回到本文最初的面試題,這道題就可以輕松解決了.(為了方便闡述,加入右邊行號(hào))
new Promise(resolve => { // 1 setTimeout(()=>{ // 2 console.log(666); // 3 new Promise(resolve => { // 4 resolve(); }) .then(() => {console.log(777);}) // 7 }) resolve(); // 9 }) // 10 .then(() => { // 11 new Promise(resolve => { // 12 resolve(); // 13 }) .then(() => {console.log(111);}) // 15 .then(() => {console.log(222);}); // 16 }) // 17 .then(() => { // 18 new Promise((resolve) => { // 19 resolve() }) .then(() => { // 22 new Promise((resolve) => { // 23 resolve() }) .then(() => {console.log(444)}) // 26 }) .then(() => { // 28 console.log(555); // 29 })}).then(() => { // 32 console.log(333);})?線程執(zhí)行第一行代碼,同步執(zhí)行Promise包裹的函數(shù).?在第二行發(fā)現(xiàn)定時(shí)器,啟動(dòng)一個(gè)宏任務(wù),將定時(shí)器的回調(diào)放入宏任務(wù)隊(duì)列等待,線程直接跳到第9行執(zhí)行?第9行執(zhí)行完開始執(zhí)行第11行代碼發(fā)現(xiàn)then函數(shù),放入當(dāng)前微任務(wù)隊(duì)列中.線程往后再?zèng)]有可以執(zhí)行的代碼了,于是開始執(zhí)行微任務(wù)隊(duì)列.?執(zhí)行微任務(wù)隊(duì)列進(jìn)入第12行代碼,運(yùn)行到第15行代碼時(shí)發(fā)現(xiàn)then函數(shù)放入微任務(wù)隊(duì)列等待.隨后線程直接跳到第18行,碰到then函數(shù)放到微隊(duì)列中.后續(xù)沒(méi)有可執(zhí)行的代碼了,再開始執(zhí)行微任務(wù)隊(duì)列的第一個(gè)任務(wù)也就是第15行代碼輸出111.?15行執(zhí)行完執(zhí)行到16行碰到then回調(diào)放入微任務(wù)隊(duì)列等待.隨后線程跳到18行的微任務(wù)開始執(zhí)行,一直執(zhí)行到22行碰到then函數(shù)又放入微任務(wù)隊(duì)列等待.此時(shí)線程繼續(xù)往下跳到第32行碰到then函數(shù)放入微任務(wù)隊(duì)列等待.后續(xù)沒(méi)有可執(zhí)行的代碼了,再開始執(zhí)行微任務(wù)隊(duì)列的第一個(gè)任務(wù).?線程跳到第16行執(zhí)行微任務(wù)輸出 222,隨后又跳到22行執(zhí)行下一個(gè)微任務(wù),在26行處碰到then函數(shù)放入微任務(wù)隊(duì)列等待.線程繼續(xù)執(zhí)行下一個(gè)微任務(wù)跳到32行輸出 333.至此這一輪的三個(gè)微任務(wù)全部執(zhí)行完畢清空,又開始執(zhí)行微任務(wù)隊(duì)列的第一個(gè)任務(wù),線程跳到第26行輸出 444.?線程執(zhí)行到28行碰到then函數(shù)回調(diào)放入微任務(wù)隊(duì)列等待.后續(xù)沒(méi)有可執(zhí)行的代碼了,再開始執(zhí)行微任務(wù)隊(duì)列的第一個(gè)任務(wù)即29行代碼輸出 555.?所有微任務(wù)執(zhí)行完畢,當(dāng)前宏任務(wù)結(jié)束.線程開始執(zhí)行下一個(gè)宏任務(wù),線程跳到第三行輸出 666.?線程繼續(xù)往后第7行碰到then回調(diào)放入微任務(wù)隊(duì)列,后續(xù)沒(méi)有可執(zhí)行的代碼了,再開始執(zhí)行微任務(wù)隊(duì)列的第一個(gè)任務(wù)輸出 777.第二個(gè)宏任務(wù)執(zhí)行完畢.
綜上所述:輸出分別為 111 -- 222 -- 333 -- 444 -- 555 -- 666 -- 777
總結(jié)
以上是生活随笔為你收集整理的定时器和promise_从Promise链理解EventLoop的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 怎么组装优盘 制作优盘的步骤
- 下一篇: 路由器下一跳地址怎么判断_网络基本功三: