想要了解Async/Await關鍵字內部是怎麼運行的?在什麼時機運行?須要提早了解js異步編程,宏任務、微任務概念。javascript
瀏覽器是多線程的,其包含以下多個線程:java
咱們一般所說javascript是單線程的
指的是js主引擎,其負責js代碼的解釋,預處理和執行。js主引擎維護一個執行棧,會按照順序依次執行放入執行棧中的代碼。若是某段代碼執行時間過長,那麼剩餘代碼會等待。ajax
console.log('excute start') const date = new Date() while (new Date() - date < 3000) { // 模擬阻塞執行棧3秒 console.log('waiting') } // 3秒後纔會執行 console.log('excute end')
可是,當咱們在js代碼中調用瀏覽器其餘的api(如定時任務)時,js主引擎就會執行異步操做,執行棧中的其餘任務會繼續執行,不用等待異步操做完成。編程
console.log('excute start') setTimeout(() => { // 3秒後當即執行 console.log('async excute') }, 3000); // 當即執行,不用等待3秒 console.log('excute end')
那麼js主引擎怎麼知道異步操做執行完畢呢?除了執行棧,還存在一個消息隊列。其餘api執行完異步操做後,會將回調事件放入消息隊列中。當執行棧中的任務所有執行完畢後,js主引擎會去查看消息隊列中是否有任務,若是有任務,就會取出最開始的任務將其放入執行棧中,執行棧會當即執行該任務。segmentfault
js代碼執行過程就是執行棧依次執行代碼,執行完成後去消息隊列中取出任務放入執行棧繼續執行,這個過程會不斷的重複,也就構成Event Loop(事件循環)。api
咱們一般使用回調函數來做爲異步任務執行完成後執行的事件,可是當回調函數嵌套過深就會造成回調地獄,嚴重影響代碼的可讀性。promise
ajax('ajax1', function () { ajax('ajax2', function () { ajax('ajax3', function () { ajax('ajax4', function () { // .... }) }) }) })
爲了解決回調函數嵌套致使的回調地獄問題,ES2015提出了Promise方案,Promise經過鏈式調用讓回調函數扁平化,減小了回調嵌套。瀏覽器
new Promise(resolve => { ajax('ajax1', function (data) { resolve(data) }) }).then(value => { return new Promise(resolve => { ajax('ajax2', function (data) { resolve(data) }) }) }).then(value => { return new Promise(resolve => { ajax('ajax3', function (data) { resolve(data) }) }) })
同時,ES中還包含Generator生成器,其配合執行器co函數也能夠解決回調函數嵌套問題。多線程
function* main() { const v1 = yield new Promise(resolve => { setTimeout(() => { resolve('promise 1') }, 1000); }) console.log(v1) const v2 = yield new Promise(resolve => { setTimeout(() => { resolve('promise 2') }, 1000); }) console.log(v2) } // 執行器 function co(gen) { let generator = gen() function handleResult(result) { if (result.done) return else { result.value.then((value) => { handleResult(generator.next(value)) }, err => { console.log(err) }) } } handleResult(generator.next()) } // 執行 co(main)
爲了更進一步簡化異步編程語法,ES2017提出Async/Await語法糖,其本質就是包裝了Generator生成器。異步
async function main() { let v1 = await new Promise(resolve => { setTimeout(() => { resolve('async excute 1') }, 1000); }) console.log(v1) let v2 = await new Promise(resolve => { setTimeout(() => { resolve('async excute 2') }, 1000); }) console.log(v2) } // 執行 main()
若是揭開語法糖,其至關於
function* main() { let v1 = yield new Promise(resolve => { setTimeout(() => { resolve('async excute 1') }, 1000); }) console.log(v1) let v2 = yield new Promise(resolve => { setTimeout(() => { resolve('async excute 2') }, 1000); }) console.log(v2) }
宏任務和微任務能夠理解爲異步任務的進一步細分,只不過執行時機不一樣,當某個宏任務執行完畢以後,會查找微任務消息隊列中是否存在微任務,若是有,那麼就執行全部微任務,而後再執行下一個宏任務。
下面是盜用的一張執行邏輯圖:
常見的生成宏任務的有:setTimeout(同等延遲時間下,setTimeOut的優先級高於setImmediate),setInterval, setImmediate, I/O, UI rendering。
常見的生成微任務的有:Promise,queueMicrotask。
setTimeout(() => { console.log('timeout') }, 0) new Promise(resolve => { resolve('promise') }).then(v => { console.log(v) })
上述代碼中,因爲Promise生成的是微任務,全部其早於setTimeout生成的宏任務,所以先輸出promise,再輸出timeout。
宏任務和微任務的執行過程能夠用下面的代碼簡要說明:
// 事件循環, 主線程 while (macroQueue.waitForMessage()) { // 1. 執行完調用棧上當前的宏任務(同步任務) // call stack // 2. 遍歷微任務隊列,把微任務隊裏上的全部任務都執行完畢(清空微任務隊列) // 微任務又能夠往微任務隊列中添加微任務 for (let i = 0; i < microQueue.length; i++) { // 獲取並執行下一個微任務(先進先出) microQueue[i].processNextMessage() } // 3. 渲染(渲染線程) // 4. 從宏任務隊列中取 一個 任務,進入下一個消息循環 macroQueue.processNextMessage(); }
上面講過,Async/Await是爲解決異步回調提出的方案,其本質是對Generator生成器進行包裝的語法糖。
async function main() { console.log('await') } console.log('next') main() // 輸出 // await // next
async function main() { const v =await new Promise(resolve => { resolve('await') }) console.log(v) } main() console.log('next') // 輸出 // next // await
async function main() { const v = await new Promise(resolve => { }) console.log(v) } main() console.log('next') // 輸出 // next
async function main() { const v = await 'await' console.log(v) } main() console.log('next') // 輸出 // next // await // 至關於 async function main() { const v = await Promise.resolve('await') console.log(v) }
async function fn() { return 'foo' // 若是沒有return,至關於return undefined } fn().then(data => { console.log(data) }) // 至關於 function fn1() { return new Promise(resolve => { resolve('foo') }) }