async/await函數

基本概念

將async和await看作命令

async/await就是generator函數的語法糖,但對generator函數有部分改進:

  • generator函數執行時返回的是一個Iterator對象,而async/await立即返回一個Promise對象,相當於能夠將函數內部的多個異步操作封裝成爲一個Promise對象。
  • async/await內部自帶執行器,也就是調用後會自動執行,而generator函數必須手動執行,或者編寫執行器。
  • await可以看作內部的then語法糖。
  • async函數遇到await時會等待後面的語句(緊跟着的一個語句)執行完成纔會繼續往下執行。此時等待的意思函數會交出執行權,讓主線程去執行其他的代碼,待完成await後的語句時,才能夠獲取執行權繼續向下執行。

async

  • 被async修飾的函數執行時會立即返回一個Promise對象。
  • 函數中的返回值會作爲Promise的then方法中resolved狀態下的參數。
    • 無返回值則是Promise.resolve(undefined)
    • 返回值是Promise,則就是返回的該Promise
    • 其他情況則是Promise.resolve(other)
  • 停止函數執行的情況
    • 函數內部任何拋出的錯誤會停止函數的執行(即並不單指Promise拋出的錯誤和rejected狀態),並將錯誤對象作爲then方法rejected狀態下的參數,或者說會被Promise.protorype.catch捕獲到。這裏拋出 的意思是如果錯誤在內部被捕獲處理,則不會停止函數執行。
    • 內部的任一rejected狀態的Promise若未在該Promise中被處理或未被try-catch語句捕獲,則也會停止當前函數的執行,會被async返回的Promise對象的catch捕獲到。

await

  • await後應該跟一個Promise對象,若不是Promise對象,將會被Promise.resolve()轉化爲Promise對象,並根據是否拋出錯誤而進行不同的操作:
    • 若無錯誤拋出則立即成爲resolved狀態,表達式的值被做爲then方法的第一個回調方法的參數。
    • 拋出錯誤則成爲rejected狀態並停止函數的執行,錯誤對象做爲catch方法的參數。

防止async函數內部出錯而終止函數剩餘代碼執行的方式

  1. 對await命令後的Promise對象使用catch方法捕獲錯誤。
  2. 將這部分代碼放入try-catch語句塊中。

使用技巧

不相關的操作之間不應該互相影響

  若一個async函數中的多個await操作之間不存在依賴關係,則不應該讓其中某些await操作等待其他的await操作的完成,應該讓他們併發執行。

async function foo(){
	await p1();
	await p2();
}

  此時p1和p2之間並無任何依賴關係,但是p2只有在p1執行完成以後纔會執行。

//method 1
async function foo(){
	await Promise.all(p1(), p2());
}
//method 2
async function foo(){
	let a1 = p1();
	let a2 = p2();
	await a1;
	await a2;
}

  上述便是可用的兩種讓相互之間無依賴的操作併發執行。

await Promise.all(…)時獲取返回值

async function foo() {
  let [a, b] =  await Promise.all([p1(), p2()]);
  console.log(a, b);
}

實現原理

  理解一下阮大佬的代碼我就滿足了[1]

//genF是generator函數
function spawn(genF) {
//直接返回一個Promise對象
	return new Promise(function(resolve, reject) {
//獲取generator的Iterator對象   
	const gen = genF();
//nextF是回調函數,根據當前Promise狀態做出不同的行爲   
    function step(nextF) {
//定義next,使其在try-catch語句塊外可見,保存Iterator上調用next的返回值{value: promise, done: boolean}
      let next;
      try {
//捕獲generator函數可能出現的異常,並使當前Promise對象成爲rejected狀態,將錯誤對象傳遞給當前Promise的catch方法
        next = nextF();
      } catch(e) {
        return reject(e);
      }
//判斷generator是否已經執行完成,完成則將返回值傳遞給then方法的第一個回調函數     
      if(next.done) {
        return resolve(next.value);
      }
//若未完成,將當前yield後的表達式轉化爲Promise對象,轉換規則見參考1
      Promise.resolve(next.value).then(function(v) {
//這裏將resolved狀態下返回的值作爲上一個yield表達式的返回值,並繼續執行下一個yield表達式,這裏的行爲發生在下一輪的step遞歸調用中
        step(function() { return gen.next(v); });
      }, function(e) {
//過程中的任意錯誤都會停止函數的執行,並拋出異常,注意這裏的使用的是gen.throw(e),直接在generator函數中拋出了異常,而非在自動執行函數中逐層向外拋出。
        step(function() { return gen.throw(e); });
      });
    }
//step函數的首次調用,generator函數執行返回的迭代器首次執行next方法時參數沒有意義,因此此時還沒有上一次執行的yield表達式
	step(function() { return gen.next(undefined); });
    });
}

  async/await函數可以看做是帶自動執行器的generator函數,因此spawn是一個generator函數的自動執行器,注意裏面的return,這裏並不是要將值返回,而是終止當前(層)函數的執行。
  雖然現在好像看懂了,估計今晚我就會忘,不要企圖手動執行去驗證超過三層的遞歸函數,否則你會知道什麼叫頭頂發涼,原理嘛理解就行。

tip

  forEach的回調函數使用async/await會發生非預期行爲,每次遍歷中的await會併發執行,原因我不太清楚,反正阮一峯大佬是這麼說的,可改爲for循環。
  async/await函數暫停執行,即函數會保留當前執行棧
  本文中的併發執行,並是操作系統中通常意義上的線程或進程併發,因爲js執行用戶代碼的線程只有一個,就是主線程,這裏是指多個任務之間的執行互不影響,不用等待其中某些任務狀態發生改變。相互之間執行的時間間隔很短,宏觀上可以看作是單核多線程不考慮同步問題的併發,且每個線程順序執行只一次。

參考

[1] 阮一峯.ES6入門
[2] 湯小丹.計算機操作系統.第4版[M]. 2014.
[3] Promise.all結合async/await

發佈了48 篇原創文章 · 獲贊 3 · 訪問量 6471
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章