什么是事件循環
事件循環又叫消息循環,是瀏覽器渲染主線程的工作方式。
瀏覽器開啟一個永不停止的for循環,每次循環都會從消息隊列中取任務,其他線程只需要在合適的時候將任務加入到消息隊列的末尾。
過去分為宏任務和微任務,現在由于瀏覽器環境越來越復雜,宏任務的說法已經不合適,取而代之的是w3c官網新提出的,每個任務都會帶有任務類型,同類型的任務放在同對列,不同的任務可以放在不同的對列,不同的對列有不同的優先級,(任務沒有優先級)由瀏覽器自行決定優先執行哪一個,但是總會有一個微隊列,優先級最高
什么是async與await
async/await是JavaScript中處理異步操作的一種方式,它是基于Promise的語法糖。
async關鍵字用于聲明一個函數是異步的,而await關鍵字用于等待一個Promise解析完成。
此時可以認為,在await之后的代碼其實是promise完成之后才執行,也就是說相當于promise.then回調執行的的內容,只是通過async / await語法糖解決了promise的回調嵌套問題
事件循環中的同步任務與異步任務
眾所周知,同步任務即按順序執行,異步任務即開啟新線程與主線程并行執行
常見的異步任務有 promise、setTimeout、nextTick等,而這些在之前又分為宏任務和微任務,但其實現在官方更改了宏任務的說法,提出分類型的任務隊列(后面統稱為其他異步隊列
)和微隊列
,微隊列的優先級高于其他異步隊列。promise.then和nextTick屬于微任務,會進入微隊列
在事件循環過程中,按順序處理當前消息隊列中的任務; 當遇到await關鍵字時,JavaScript會將該異步函數暫停(也就是指暫停當前 async function 內部await之后的代碼執行)
其實此時await后面的代碼產生一個微任務,進入微隊列,瀏覽器主線程將其放入微隊列后將繼續執行消息隊列中的任務,直到消息隊列中的任務全部執行完成之后,微隊列的任務才會進入到消息隊列去執行
需要注意的是,雖然await會暫停代碼執行,但它不會阻塞事件循環。這意味著其他任務(已在消息隊列中的后面的任務)可以在等待Promise解析期間繼續執行。
案例
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)// 4 1 3 6 8 2 7 5
解析
首先解析這些js代碼,將任務按照代碼執行順序放入消息隊列,初始隊列如下圖
開始執行之后
1、首先控制臺輸出4
2、其次執行setTimeout,為異步任務,并且不是微任務,放入其他異步隊列,交給其他線程進行處理
3、執行到async1(),進入async1()函數內部執行
async function async1 () {console.log(1)await async2()console.log(2)
}
進入async1()函數內部開始執行
1、控制臺輸出1
2、進入async2()執行并等待,也就是將await async2()
后面的內容console.log(2)
放入微隊列
等待promise解析完成后放入消息隊列。而async2()內部只有console.log(3)
,是同步任務,立即執行,控制臺輸出3。此時的消息隊列圖如下
繼續執行,進入new Promise()
1、控制臺輸出6
2、.then()放入微隊列
3、繼續執行控制臺輸出8,此時的消息隊列圖如下
消息隊列清空后,微隊列的任務進入消息隊列,繼續執行
1、控制臺輸出2
2、執行then函數,輸出7
3、微隊列任務清空,其他異步隊列任務進入消息隊列開始執行
控制臺輸出5,至此所有任務執行完畢,控制臺輸出 4 1 3 6 8 2 7 5