async 函數的實現原理

基本概念

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); });
  });
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章