js 異步執(zhí)行順序

js的執(zhí)行順序山叮,先同步后異步
異步中任務(wù)隊列的執(zhí)行順序: 先微任務(wù)microtask隊列著榴,再宏任務(wù)macrotask隊列
調(diào)用Promise 中的resolvereject屬于微任務(wù)隊列屁倔,setTimeout屬于宏任務(wù)隊列
注意以上都是 隊列,先進先出暮胧。
微任務(wù)包括 process.nextTick 锐借,promiseMutationObserver往衷。
宏任務(wù)包括 script 钞翔, setTimeoutsetInterval 席舍,setImmediate 布轿,I/OUI rendering

事件循環(huán)是什么

首先汰扭,JavaScript是一門單線程的語言稠肘,意味著同一時間內(nèi)只能做一件事,但是這并不意味著單線程就是阻塞萝毛,而實現(xiàn)單線程非阻塞的方法就是事件循環(huán)

在JavaScript中项阴,所有的任務(wù)都可以分為

  • 同步任務(wù):立即執(zhí)行的任務(wù),同步任務(wù)一般會直接進入到主線程中執(zhí)行
  • 異步任務(wù):異步執(zhí)行的任務(wù)笆包,比如ajax網(wǎng)絡(luò)請求环揽,setTimeout定時函數(shù)等

同步任務(wù)與異步任務(wù)的運行流程圖如下:

image.png

從上面我們可以看到,同步任務(wù)進入主線程庵佣,即主執(zhí)行棧歉胶,異步任務(wù)進入任務(wù)隊列,主線程內(nèi)的任務(wù)執(zhí)行完畢為空巴粪,會去任務(wù)隊列讀取對應(yīng)的任務(wù)跨扮,推入主線程執(zhí)行。上述過程的不斷重復(fù)就事件循環(huán)

調(diào)用棧(Call Stack)

是一種后進先出的數(shù)據(jù)結(jié)構(gòu)验毡。當(dāng)一個腳本執(zhí)行的時候衡创,js引擎會解析這段代碼,并將其中的同步代碼按照執(zhí)行順序加入調(diào)用棧中晶通,然后從頭開始執(zhí)行璃氢。

事件隊列 (Task Queue)

js引擎遇到一個異步任務(wù)后并不會一直等待其返回結(jié)果,而是會將這個任務(wù)交給瀏覽器的其他模塊進行處理(以webkit為例狮辽,是webcore模塊)一也,繼續(xù)執(zhí)行調(diào)用棧中的其他任務(wù)。當(dāng)一個異步任務(wù)返回結(jié)果后喉脖,js引擎會將這個任務(wù)加入與當(dāng)前調(diào)用棧不同的另一個隊列椰苟,我們稱之為事件隊列。

當(dāng)一個腳本執(zhí)行的時候树叽,js引擎會解析這段代碼舆蝴,并將其中的同步代碼按照執(zhí)行順序加入調(diào)用棧中,然后從頭開始執(zhí)行题诵。

js引擎遇到一個異步事件后并不會一直等待其返回結(jié)果洁仗,而是會將這個事件掛起(其他模塊進行處理),繼續(xù)執(zhí)行執(zhí)行棧中的其他任務(wù)性锭。當(dāng)一個異步事件返回結(jié)果后赠潦,js會將這個事件加入到事件隊列。

被放入事件隊列不會立刻執(zhí)行其回調(diào)草冈,而是等待當(dāng)前執(zhí)行棧中的所有任務(wù)都執(zhí)行完畢她奥, 主線程處于閑置狀態(tài)時瓮增,主線程會去查找事件隊列是否有任務(wù)。如果有哩俭,那么主線程會從中取出排在第一位的事件绷跑,并把這個事件對應(yīng)的回調(diào)放入執(zhí)行棧中,然后執(zhí)行其中的同步代碼…携茂,如此反復(fù)你踩,這樣就形成了一個無限的循環(huán)。這個過程被稱為“事件循環(huán)(Event Loop)”讳苦。

同步任務(wù)

首先带膜,我們用一個棧來表示主線程

image.png

當(dāng)有多個同步任務(wù)時,這些同步任務(wù)會依次入棧出棧鸳谜,如下圖

image.png

同步任務(wù)1先入棧膝藕,執(zhí)行完之后,出棧咐扭,接著同步任務(wù)2入棧芭挽,依此類推

這只是同步任務(wù)的執(zhí)行方式,那么異步任務(wù)呢蝗肪?

異步任務(wù)

異步任務(wù)會在同步任務(wù)執(zhí)行之后再去執(zhí)行袜爪,那么如果異步任務(wù)代碼在同步任務(wù)代碼之前呢?在js機制里薛闪,存在一個隊列辛馆,叫做任務(wù)隊列,專門用來存放異步任務(wù)豁延。也就是說昙篙,當(dāng)異步任務(wù)出現(xiàn)的時候,會先將異步任務(wù)存放在任務(wù)隊列中诱咏,當(dāng)執(zhí)行完所有的同步任務(wù)之后苔可,再去調(diào)用任務(wù)隊列中的異步任務(wù)

例如下圖,現(xiàn)在存在兩個同步任務(wù)袋狞,兩個異步任務(wù)

image.png

js會先將同步任務(wù)1提至主線程焚辅,然后發(fā)現(xiàn)異步任務(wù)1和2,則將異步任務(wù)1和2依次放入任務(wù)隊列

image.png

異步任務(wù)分類

js中硕并,又將異步任務(wù)分為宏任務(wù)和微任務(wù)法焰,所以,上述任務(wù)隊列也分為宏任務(wù)隊列和微任務(wù)隊列倔毙,那么,什么是宏任務(wù)乙濒,什么是微任務(wù)呢陕赃?

I/O卵蛉、定時器、事件綁定么库、ajax等都是宏任務(wù)

Promise的then傻丝、catch、finally和process的nextTick都是微任務(wù)

注意:Promise的then等方法是微任務(wù)诉儒,而Promise中的代碼是同步任務(wù)葡缰,并且,nextTick的執(zhí)行順序優(yōu)先于Promise的then等方法忱反,因為nextTick是直接告訴瀏覽器說要盡快執(zhí)行泛释,而不是放入隊列

js中,微任務(wù)總是先于宏任務(wù)執(zhí)行温算,也就是說怜校,這三種任務(wù)的執(zhí)行順序是:同步任務(wù)>微任務(wù)>宏任務(wù)

宏任務(wù)與微任務(wù)

如果將任務(wù)劃分為同步任務(wù)和異步任務(wù)并不是那么的準(zhǔn)確,舉個例子:

console.log(1)
setTimeout(()=>{    
  console.log(2)}, 0)
new Promise((resolve, reject)=>{    
    console.log('new Promise')    
    resolve()
  }).then(()=>{    
    console.log('then')
})
console.log(3)

如果按照上面流程圖來分析代碼注竿,我們會得到下面的執(zhí)行步驟:

  • console.log(1)茄茁,同步任務(wù),主線程中執(zhí)行
  • setTimeout() 巩割,異步任務(wù)裙顽,放到 Event Table,0 毫秒后console.log(2)回調(diào)推入 Event Queue 中
  • new Promise 宣谈,同步任務(wù)愈犹,主線程直接執(zhí)行
  • .then ,異步任務(wù)蒲祈,放到 Event Table
  • console.log(3)甘萧,同步任務(wù),主線程執(zhí)行

所以按照分析梆掸,它的結(jié)果應(yīng)該是 1 => 'new Promise' => 3 => 2 => 'then'

但是實際結(jié)果是:1=>'new Promise'=> 3 => 'then' => 2

出現(xiàn)分歧的原因在于異步任務(wù)執(zhí)行順序扬卷,事件隊列其實是一個“先進先出”的數(shù)據(jù)結(jié)構(gòu),排在前面的事件會優(yōu)先被主線程讀取

例子中 setTimeout回調(diào)事件是先進入隊列中的酸钦,按理說應(yīng)該先于 .then 中的執(zhí)行怪得,但是結(jié)果卻偏偏相反

原因在于異步任務(wù)還可以細(xì)分為微任務(wù)與宏任務(wù)

微任務(wù)

一個需要異步執(zhí)行的函數(shù),執(zhí)行時機是在主函數(shù)執(zhí)行結(jié)束之后卑硫、當(dāng)前宏任務(wù)結(jié)束之前

常見的微任務(wù)有:

  • Promise.then
  • MutaionObserver
  • Object.observe(已廢棄徒恋;Proxy 對象替代)
  • process.nextTick(Node.js)

宏任務(wù)

宏任務(wù)的時間粒度比較大,執(zhí)行的時間間隔是不能精確控制的欢伏,對一些高實時性的需求就不太符合

常見的宏任務(wù)有:

  • script (可以理解為外層同步代碼)
  • setTimeout/setInterval
  • UI rendering/UI事件
  • postMessage入挣、MessageChannel
  • setImmediate、I/O(Node.js)

這時候硝拧,事件循環(huán)径筏,宏任務(wù)葛假,微任務(wù)的關(guān)系如圖所示

image.png

按照這個流程,它的執(zhí)行機制是:

  • 執(zhí)行一個宏任務(wù)滋恬,如果遇到微任務(wù)就將它放到微任務(wù)的事件隊列中
  • 當(dāng)前宏任務(wù)執(zhí)行完成后聊训,會查看微任務(wù)的事件隊列,然后將里面的所有微任務(wù)依次執(zhí)行完

回到上面的題目

console.log(1)
setTimeout(()=>{    
  console.log(2)}, 0)
new Promise((resolve, reject)=>{    
    console.log('new Promise')    
    resolve()
  }).then(()=>{    
    console.log('then')
})
console.log(3)

流程如下

遇到 console.log(1) 恢氯,直接打印 1
遇到定時器带斑,屬于新的宏任務(wù),留著后面執(zhí)行
遇到 new Promise勋拟,這個是直接執(zhí)行的勋磕,打印 'new Promise'
.then 屬于微任務(wù),放入微任務(wù)隊列指黎,后面再執(zhí)行
遇到 console.log(3) 直接打印 3
好了本輪宏任務(wù)執(zhí)行完畢朋凉,現(xiàn)在去微任務(wù)列表查看是否有微任務(wù),發(fā)現(xiàn) .then 的回調(diào)醋安,執(zhí)行它杂彭,打印 'then'
當(dāng)一次宏任務(wù)執(zhí)行完,再去執(zhí)行新的宏任務(wù)吓揪,這里就剩一個定時器的宏任務(wù)了亲怠,執(zhí)行它,打印 2

async與await

async 是異步的意思柠辞,await則可以理解為 async wait团秽。所以可以理解async就是用來聲明一個異步方法,而 await是用來等待異步方法執(zhí)行

async和await是es7提供的語法叭首,相比于es6的promise 习勤,具有更高的代碼可讀性

從字面意思理解async是異步的意思,await是等待的意思焙格,那么他們的作用就很容易看出了:

async : 聲明一個函數(shù)是異步的

await : 等待一個異步函數(shù)執(zhí)行完成

語法注意:await必須聲明在async內(nèi)部图毕,因為async會阻斷后邊代碼的執(zhí)行,說到阻斷大家不要慌眷唉,因為這里的阻斷都是在一個async聲明的promise函數(shù)里的阻斷予颤,不會影響整體代碼的執(zhí)行,async外邊的代碼還是照常執(zhí)行

async語法的優(yōu)勢

async語法在處理then鏈的時候有優(yōu)勢冬阳,我們?nèi)绻胮romise的then嵌套可能會寫出多個then鏈蛤虐,雖然解決了回調(diào)地獄的問題,但是感覺好亂啊

用async和await實現(xiàn)多接口調(diào)用的代碼:

function callServe1(){
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            resolve({result:"callserve1"})
        }, 1000);
    })}
function callServe2(res){
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            resolve(res)
        }, 1000);
    })}
function callServe3(res){
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            resolve(res)
        }, 1000);
    })}
async function getAll(){
    const result1 = await callServe1()
    const result2 = await callServe2(result1)
    const result3 = await callServe3(result2)
    console.log(result3) //{ result: 'callserve1' }}
getAll()

async

1.函數(shù)前面加上 async 關(guān)鍵字肝陪,則該函數(shù)會返回一個結(jié)果為 promise 的對象驳庭。

2. async 函數(shù)返回 promise 對象的狀態(tài)。

2.1:如果返回的是一個非 Promise 類型的數(shù)據(jù)氯窍, 則async 函數(shù)返回 promise 的狀態(tài) 為 fulfilled 成功嚷掠。

2.2:如果返回的是一個 Promise對象捏检,則 async 函數(shù)返回 promise 的狀態(tài)由返回的Promise對象的狀態(tài)決定荞驴。

2.3:如果 throw Errow 拋出異常不皆,則 async 函數(shù)返回 promise 的狀態(tài)為 rejected 失敗。

async函數(shù)返回一個promise對象熊楼,下面兩種方法是等效的

function f() {
    return Promise.resolve('TEST');
}
async function asyncF() {
    return 'TEST';
}

await

1.await 右側(cè)的表達式一般為 promise 對象霹娄。

2.await 是在等一個表達式的結(jié)果,等一個返回值(回調(diào)成功的內(nèi)容)

3.如果 await 右側(cè)是一個 promise 對象鲫骗,則 await 返回的是 promise 成功的值犬耻。

注意:

1.await 必須寫在 async 函數(shù)中,但 async 函數(shù)中可以沒有 await执泰。

2.如果 await 的 promise 失敗了枕磁,就會拋出異常,該異常需要通過 try catch 捕獲處理术吝。

3.如果它等到的是一個 Promise 對象计济,await 就忙起來了,它會阻塞后面的代碼排苍,等著 Promise 對象 resolve沦寂,然后得到 resolve 的值,作為 await 表達式的運算結(jié)果淘衙。

正常情況下传藏,await命令后面是一個 Promise對象,返回該對象的結(jié)果彤守。如果不是 Promise對象毯侦,就直接返回對應(yīng)的值

async function f(){    // 等同于    // return 123    return await 123}f().then(v => console.log(v)) // 123

不管await后面跟著的是什么,await都會阻塞后面的代碼

async function fn1 (){
    console.log(1)
    await fn2()
    console.log(2) // 阻塞
}
async function fn2 (){
    console.log('fn2')
}
fn1()
console.log(3)

上面的例子中具垫,await 會阻塞下面的代碼(即加入微任務(wù)隊列)侈离,先執(zhí)行 async外面的同步代碼,同步代碼執(zhí)行完做修,再回到 async 函數(shù)中霍狰,再執(zhí)行之前阻塞的代碼

所以上述輸出結(jié)果為:1,fn2饰及,3蔗坯,2

流程分析

通過對上面的了解,我們對JavaScript對各種場景的執(zhí)行順序有了大致的了解

這里直接上代碼:

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2')
}
console.log('script start')
setTimeout(function () {
    console.log('settimeout')
})
async1()new Promise(function (resolve) {
    console.log('promise1')
    resolve()}).then(function () {
    console.log('promise2')
})
console.log('script end')

分析過程:

  1. 執(zhí)行整段代碼燎含,遇到 console.log('script start') 直接打印結(jié)果宾濒,輸出 script start
  2. 遇到定時器了,它是宏任務(wù)屏箍,先放著不執(zhí)行
  3. 遇到 async1()绘梦,執(zhí)行 async1 函數(shù)橘忱,先打印 async1 start,下面遇到await怎么辦卸奉?先執(zhí)行 async2钝诚,打印 async2,然后阻塞下面代碼(即加入微任務(wù)列表)榄棵,跳出去執(zhí)行同步代碼
  4. 跳到 new Promise 這里凝颇,直接執(zhí)行,打印 promise1疹鳄,下面遇到 .then()拧略,它是微任務(wù),放到微任務(wù)列表等待執(zhí)行
  5. 最后一行直接打印 script end瘪弓,現(xiàn)在同步代碼執(zhí)行完了垫蛆,開始執(zhí)行微任務(wù),即 await下面的代碼腺怯,打印 async1 end
  6. 繼續(xù)執(zhí)行下一個微任務(wù)袱饭,即執(zhí)行 then 的回調(diào),打印 promise2
  7. 上一個宏任務(wù)所有事都做完了瓢喉,開始下一個宏任務(wù)宁赤,就是定時器,打印 settimeout

所以最后的結(jié)果是:script start栓票、async1 start决左、async2、promise1走贪、script end佛猛、async1 end、promise2坠狡、settimeout

題目如下:

async function async1(){
    console.log('1')
    await async2()
    console.log('2')
}
async function async2(){
    console.log('3')
}
console.log('4')
setTimeout(function(){
    console.log('5') 
},0)  
async1();
new Promise(function(resolve){
    console.log('6')
    resolve();
}).then(function(){
    console.log('7')
})
console.log('8')

答案如下:
继找。。逃沿。婴渡。。凯亮。边臼。。

原文鏈接:js原理之事件循環(huán)Event Loop 宏任務(wù)與微任務(wù) async與await

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末假消,一起剝皮案震驚了整個濱河市柠并,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖臼予,帶你破解...
    沈念sama閱讀 211,496評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸣戴,死亡現(xiàn)場離奇詭異,居然都是意外死亡粘拾,警方通過查閱死者的電腦和手機窄锅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來半哟,“玉大人酬滤,你說我怎么就攤上這事≡⒄牵” “怎么了?”我有些...
    開封第一講書人閱讀 157,091評論 0 348
  • 文/不壞的土叔 我叫張陵氯檐,是天一觀的道長戒良。 經(jīng)常有香客問我,道長冠摄,這世上最難降的妖魔是什么糯崎? 我笑而不...
    開封第一講書人閱讀 56,458評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮河泳,結(jié)果婚禮上沃呢,老公的妹妹穿的比我還像新娘。我一直安慰自己拆挥,他們只是感情好薄霜,可當(dāng)我...
    茶點故事閱讀 65,542評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著纸兔,像睡著了一般惰瓜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上汉矿,一...
    開封第一講書人閱讀 49,802評論 1 290
  • 那天崎坊,我揣著相機與錄音,去河邊找鬼洲拇。 笑死奈揍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赋续。 我是一名探鬼主播男翰,決...
    沈念sama閱讀 38,945評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蚕捉!你這毒婦竟也來了奏篙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,709評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎秘通,沒想到半個月后为严,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,158評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡肺稀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,502評論 2 327
  • 正文 我和宋清朗相戀三年第股,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片话原。...
    茶點故事閱讀 38,637評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡夕吻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出繁仁,到底是詐尸還是另有隱情涉馅,我是刑警寧澤,帶...
    沈念sama閱讀 34,300評論 4 329
  • 正文 年R本政府宣布黄虱,位于F島的核電站稚矿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捻浦。R本人自食惡果不足惜晤揣,卻給世界環(huán)境...
    茶點故事閱讀 39,911評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望朱灿。 院中可真熱鬧昧识,春花似錦、人聲如沸盗扒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,744評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽环疼。三九已至习霹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間炫隶,已是汗流浹背淋叶。 一陣腳步聲響...
    開封第一講書人閱讀 31,982評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留伪阶,地道東北人煞檩。 一個月前我還...
    沈念sama閱讀 46,344評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像栅贴,于是被迫代替她去往敵國和親斟湃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,500評論 2 348

推薦閱讀更多精彩內(nèi)容