異步編程的最高境界就是不關心它是否是異步。async、await很好的解決了這一點,將異步強行轉換為同步處理。
async/await與promise不存在誰代替誰的說法,因為async/await是寄生於Promise,是Generater的語法糖。
溫馨提示:如果你已經知道了關於async await的基本用法,請直接看分割線以下內容
Generator
function* helloWorld () { yield 'hello' yield 'world' return 'ending' } var hw = helloWorld() console.log(hw) // helloWorld {<suspended>} console.log(hw.next()) // {value: "hello", done: false} console.log(hw.next()) // {value: "world", done: false} console.log(hw.next()) // {value: "ending", done: false} console.log(hw.next()) // {value: undefined, done: true}
async function fn() { await console.log(1111111) await console.log(2222222) await console.log(3333333) } fn() // 1111111 // 2222222 // 3333333
async function fn () { await 100 await 200 return 300 } fn().then(res => { console.log(res) // 300 })

打印結果如下:(返回的是promise對象)
如果在async函數中拋出了錯誤,則終止錯誤結果,不會繼續向下執行:
async function f() { try { await Promise.reject('出錯了'); } catch(e) { } return await Promise.resolve('hello world'); } f() .then(v => console.log(v)) // hello world // catch async function f() { await Promise.reject('出錯了') .catch(e => console.log(e)); // 出錯了 return await Promise.resolve('hello world'); } f() .then(v => console.log(v)) // hello world
================================ 分割線 ==================================
面試題
【例1】
async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); return 'async return'; } async function async2() { console.log("async2"); } console.log("script start"); setTimeout(function() { console.log("setTimeout"); }, 0); async1().then(function (message) { console.log(message) }); new Promise(function(resolve) { console.log("promise1"); resolve(); }).then(function() { console.log("promise2"); }); console.log("script end");
輸出順序如下:
這道題目考查的是我們對 事件循環 任務隊列 的理解:
事件循環(Event Loop): 1. JS會首先判斷代碼是同步還是異步,同步進入主線程,異步進入任務隊列; 2. 同步任務進入主線程后一直執行,直到主線程空閑后,才會去任務隊列中查看是否有可執行的異步任務,如果有就推入主線程中執行; 3. 事件循環是一個先進先出(FIFO)隊列,這說明回調是按照它們被加入隊列的順序執行的。
[ 分析 ]:
1. 在單線程的js中,異步代碼會被放入一個事件隊列,等到所有其他代碼執行后再執行,而不會阻塞線程。我們從上到下看,首先打印:script start;
2. setTimeout / setInterval 要放到任務隊列的末尾,等待后續執行。繼續往下走;
此時的任務隊列: setTimeout
3. async1 開始執行,當函數里遇到await時,暫停執行(await所在行放在本次執行完),而 async1 函數 未完成部分被添加到任務隊列;
此時的任務隊列: async1 setTimeout
4. new Promise() 實例對象被new出來后,它里面的promise1會立刻打印,然后又遇到 then, 此時 promise 實例 被添加到任務隊列;
此時的任務隊列: async1 promise實例 setTimeout
5. 接着打印:script end。至此,同步代碼已執行完畢。
而我們的任務隊列中還存在着 async1, promise對象, setTimeout異步回調;
6. 由於異步代碼第一次執行時,async1 函數 要早於 promise對象,所以緊接着 async1 函數繼續執行沒有執行完成的部分,執行完畢后,退出任務隊列,打印:async1 end。然后把它的 then 邏輯添加到任務 隊列中;
此時的任務隊列: promise實例 async1的then邏輯部分 setTimeout
7. promise 實例 繼續執行它的 then 的邏輯,打印:promise2。執行完畢后,退出任務隊列;
此時的任務隊列: async1的then邏輯部分 setTimeout
8. async 函數執行 then 邏輯;
此時的任務隊列: setTimeout
9. setTimeout是宏任務會在最后執行。
【 補充說明 】:
1. 因為在 async1 函數內部被一個 await 分為兩部分,需要分兩步才可執行完。3的時候執行完第一步后暫停,而將剩余部分放到任務隊列等待執行;
2. 在5的時候同步代碼已執行完畢,所以 js 回過頭來去任務隊列上找未完成的異步任務,這個時候首先去執行 async1(在6時候), 因為它最先被放到任務隊列;
3. 在6時候,async1 函數並沒有緊接着執行 then 的邏輯,而是繼續執行沒有執行完成的部分,而這次當 async1 執行完畢之后,會把 then 放到任務隊列當中,且排在promise對象之后。7的時候promise 實例繼續執行下一步異步代碼,執行完畢之后,任務隊列此時只剩下 async1 的 then 邏輯,這時執行棧會執行 async1 的 then 邏輯。
【例2】:
var p = new Promise((res,rej) => { res('hello one') console.log('good morning') }) function hello() { console.log('hello begins') return p } hello().then(res => { console.log(res) console.log('hello1111111111') return 'hello two' }).then(res => { console.log(res) console.log('hello22222222222') return 'hello three' }).then(res => { console.log(res) console.log('hello33333333333') }) function test1 () { console.log('test1') } async function asy () { console.log('asy begins') await console.log('asy---111111') console.log('async1') await console.log('asy---222222') console.log('asy ends') } asy() test1() function* gnrt () { console.log(1) yield console.log(11111111) console.log(2) yield console.log(22222222) console.log(3) yield console.log(33333333) } var result = gnrt() result.next() result.next() result.next()
輸出順序如下: