async 函數的含義和用法

1、終極解決

異步操做是 JavaScript 編程的麻煩事,麻煩到一直有人提出各類各樣的方案,試圖解決這個問題。shell

從最先的回調函數,到 Promise 對象,再到 Generator 函數,每次都有所改進,但又讓人以爲不完全。它們都有額外的複雜性,都須要理解抽象的底層運行機制。編程

異步I/O不就是讀取一個文件嗎,幹嗎要搞得這麼複雜?異步編程的最高境界,就是根本不用關心它是否是異步。promise

async 函數就是隧道盡頭的亮光,不少人認爲它是異步操做的終極解決方案。bash

2、async 函數是什麼?

一句話,async 函數就是 Generator 函數的語法糖。併發

前文有一個 Generator 函數,依次讀取兩個文件。異步

var fs = require('fs');

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

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

寫成 async 函數,就是下面這樣。async

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,僅此而已。異步編程

3、async 函數的優勢

async 函數對 Generator 函數的改進,體如今如下三點。函數

(1)內置執行器。 Generator 函數的執行必須靠執行器,因此纔有了 co 函數庫,而 async 函數自帶執行器。也就是說,async 函數的執行,與普通函數如出一轍,只要一行。post

var result = asyncReadFile();
複製代碼

(2)更好的語義。 async 和 await,比起星號和 yield,語義更清楚了。async 表示函數裏有異步操做,await 表示緊跟在後面的表達式須要等待結果。

(3)更廣的適用性。 co 函數庫約定,yield 命令後面只能是 Thunk 函數或 Promise 對象,而 async 函數的 await 命令後面,能夠跟 Promise 對象和原始類型的值(數值、字符串和布爾值,但這時等同於同步操做)。

4、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) {
    var gen = genF();
    function step(nextF) {
      try {
        var 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); });
  });
}
複製代碼

async 函數是很是新的語法功能,新到都不屬於 ES6,而是屬於 ES7。目前,它仍處於提案階段,可是轉碼器 Babel 和 regenerator 都已經支持,轉碼後就能使用。

5、async 函數的用法

同 Generator 函數同樣,async 函數返回一個 Promise 對象,可使用 then 方法添加回調函數。當函數執行的時候,一旦遇到 await 就會先返回,等到觸發的異步操做完成,再接着執行函數體內後面的語句。

下面是一個例子。

async function getStockPriceByName(name) {
  var symbol = await getStockSymbol(name);
  var stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result){
  console.log(result);
});

複製代碼

上面代碼是一個獲取股票報價的函數,函數前面的async關鍵字,代表該函數內部有異步操做。調用該函數時,會當即返回一個Promise對象。

下面的例子,指定多少毫秒後輸出一個值。

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value)
}

asyncPrint('hello world', 50);

複製代碼

上面代碼指定50毫秒之後,輸出"hello world"。

6、注意點

await 命令後面的 Promise 對象,運行結果多是 rejected,因此最好把 await 命令放在 try...catch 代碼塊中。

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另外一種寫法

async function myFunction() {
  await somethingThatReturnsAPromise().catch(function (err){
    console.log(err);
  });
}
複製代碼

await 命令只能用在 async 函數之中,若是用在普通函數,就會報錯。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 報錯
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

複製代碼

上面代碼會報錯,由於 await 用在普通函數之中了。可是,若是將 forEach 方法的參數改爲 async 函數,也有問題。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 可能獲得錯誤結果
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}

複製代碼

上面代碼可能不會正常工做,緣由是這時三個 db.post 操做將是併發執行,也就是同時執行,而不是繼發執行。正確的寫法是採用 for 循環。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}

複製代碼

若是確實但願多個請求併發執行,可使用 Promise.all 方法。

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// 或者使用下面的寫法

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}複製代碼
相關文章
相關標籤/搜索