1. 程式人生 > 實用技巧 >ECMAScript 6-async函式

ECMAScript 6-async函式

含義

async 函式是什麼?一句話,它就是 Generator 函式的語法糖

//依次讀取兩個檔案
var asyncReadFile = async function () {
  var f1 = await readFile('/etc/fstab');
  var f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
//一比較就會發現,async函式就是將 Generator 函式的星號(*)替換成async,將yield替換成await,僅此而已

async

函式對 Generator 函式的改進

(1)內建執行器:Generator 函式的執行必須靠執行器,所以才有了co模組,而async函式自帶執行器。

var result = asyncReadFile()
//呼叫了asyncReadFile函式,然後它就會自動執行,輸出最後結果。這完全不像 Generator 函式,需要呼叫next方法,或者用co模組,才能真正執行,得到最後結果。

(2)更廣的適用性:co模組約定,yield命令後面只能是 Thunk 函式或 Promise 物件,而async函式的await命令後面,可以是Promise 物件和原始型別的值(數值、字串和布林值,但這時等同於同步操作)

(3)返回值是 Promise:async函式的返回值是 Promise 物件,這比 Generator 函式的返回值是 Iterator 物件方便多了。你可以用then方法指定下一步的操作

基本用法

async函式返回一個 Promise 物件,可以使用then方法添加回調函式。當函式執行的時候,一旦遇到await就會先返回,等到非同步操作完成,再接著執行函式體內後面的語句

async function getStockPriceByName(name) {
  var symbol = await getStockSymbol(name);//依次自動執行,返回一個promise物件
  var stockPrice = await getStockPrice(symbol);
  return stockPrice;//return返回的值會成為then方法的回撥引數
}

getStockPriceByName('goog').then(function (result) {//result上面返回的值
  console.log(result);
});

async 函式有多種使用形式

// 函式宣告
async function foo() {}
// 函式表示式
const foo = async function () {};
// 物件的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
// 箭頭函式
const foo = async () => {};

語法

  • async函式返回一個 Promise 物件。async函式內部return語句返回的值,會成為then方法回撥函式的引數

    async function f() {
      return 'hello world';
    }
    
    f().then(v => console.log(v)) //返回值“hello world”被當做引數
    // "hello world"
    
  • async函式內部丟擲錯誤,會導致返回的 Promise 物件變為reject狀態。丟擲的錯誤物件會被catch方法回撥函式接收到

    async function f() {
      throw new Error('出錯了');
    }
    
    f().then(
      v => console.log(v),//resoleved狀態時的回撥
      e => console.log(e)//reject狀態時的回撥 這裡呼叫這個
    )
    // Error: 出錯了
    
  • Promise 物件的狀態變化

    只有async函式內部的非同步操作執行完,(或者遇到return語句或者丟擲錯誤,才會執行then方法指定的回撥函式

  • await 命令

    正常情況下,await命令後面是一個 Promise 物件。如果不是,會被轉成一個立即resolve的 Promise 物件

    async function f() {
      return await 123;//await後時數值123,它被轉成 Promise 物件,並立即resolve
    }
    
    f().then(v => console.log(v))	// 123
    
    

    await命令後面的 Promise 物件如果變為reject狀態,則reject的引數會被catch方法的回撥函式接收到。只要一個await語句後面的 Promise 變為reject,那麼整個async函式都會中斷執行

    async function f() {
      await Promise.reject('出錯了');
    }
    
    f()
    .then(v => console.log(v))
    .catch(e => console.log(e))
    // 出錯了
    //注意,這裡await語句前面沒有return,但是reject方法的引數依然傳入了catch方法的回撥函式。加上return效果也一樣
    

    如果希望即使前一個非同步操作失敗,也不要中斷後面的非同步操作。這時可以將第一個await放在try...catch結構裡面

    async function f() {
      try {
        await Promise.reject('出錯了');
      } catch(e) {}
      return await Promise.resolve('hello world');
    }
    
    f()
    .then(v => console.log(v))
    // hello world
    

    另一種方法是await後面的 Promise 物件再跟一個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
    
  • 錯誤處理

    如果await後面的非同步操作出錯,那麼等同於async函式返回的 Promise 物件被reject

    async function f() {
      await new Promise(function (resolve, reject) {
        throw new Error('出錯了');
      });
    }
    
    f()
    .then(v => console.log(v))
    .catch(e => console.log(e))
    // Error:出錯了
    

    防止出錯的方法(防止狀態變為reject),就是將其放在try...catch程式碼塊之中

    async function f() {
      try {
        await new Promise(function (resolve, reject) {
          throw new Error('出錯了');
        });
      } catch(e) {
      }
      return await('hello world');
    }
    
    //如果有多個await命令,可以統一放在try...catch結構中
    async function main() {
      try {
        var val1 = await firstStep();
        var val2 = await secondStep(val1);
        var val3 = await thirdStep(val1, val2);
    
        console.log('Final: ', val3);
      }
      catch (err) {
        console.error(err);
      }
    }
    

注意

  • await命令後面的Promise物件,執行結果可能是rejected,所以最好把await命令放在try...catch程式碼塊中

  • 多個await命令後面的非同步操作,如果不存在繼發關係,最好讓它們同時觸發。

    //原始是繼發關係,耗時==>由於這兩個都是獨立的非同步操作,最好寫成同步觸發
    let foo = await getFoo();
    let bar = await getBar();
    
    // 寫法一
    let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    // 寫法二
    let fooPromise = getFoo();
    let barPromise = getBar();
    let foo = await fooPromise;
    let bar = await barPromise;
    
  • await命令只能用在async函式之中,如果用在普通函式,就會報錯

async 函式的實現原理就是將 Generator 函式和自動執行器,包裝在一個函式裡。