async 函式的實現原理

wdapp發表於2020-02-25

基本概念

ES2017 標準引入了 async 函式,使得非同步操作變得更加方便。

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

前文有一個 Generator 函式,依次讀取兩個檔案。

const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
複製程式碼

上面程式碼的函式gen可以寫成async函式,就是下面這樣。

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
複製程式碼

一比較就會發現,async函式就是將 Generator 函式的星號(*)替換成async,將yield替換成await,僅此而已。

async函式對 Generator 函式的改進,體現在以下四點。

(1)內建執行器。

Generator 函式的執行必須靠執行器,所以才有了co模組,而async函式自帶執行器。也就是說,async函式的執行,與普通函式一模一樣,只要一行。

asyncReadFile();
複製程式碼

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

(2)更好的語義。

async和await,比起星號和yield,語義更清楚了。async表示函式裡有非同步操作,await表示緊跟在後面的表示式需要等待結果。

(3)更廣的適用性。

co模組約定,yield命令後面只能是 Thunk 函式或 Promise 物件,而async函式的await命令後面,可以是 Promise 物件和原始型別的值(數值、字串和布林值,但這時會自動轉成立即 resolved 的 Promise 物件)。

(4)返回值是 Promise。

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

進一步說,async函式完全可以看作多個非同步操作,包裝成的一個 Promise 物件,而await命令就是內部then命令的語法糖。

async 函式的實現原理

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

async function fn(args) {
  // ...
}

// 等同於

function fn(args) {
  return spawn(function* () {
    // ...
  });
}
複製程式碼

所有的async函式都可以寫成上面的第二種形式,其中的spawn函式就是自動執行器。

下面給出spawn函式的實現,基本就是前文自動執行器的翻版。

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}
複製程式碼

相關文章