ES7 中使用 async/await 解決回調函數嵌套問題

JavaScript 中最蛋疼的事情莫過於回調函數嵌套問題。以往在瀏覽器中,因爲與服務器通訊是一種比較昂貴的操作,因此比較複雜的業務邏輯往往都放在服務器端,前端 JavaScript 只需要少數幾次 AJAX 請求就可拿到全部數據。

但是到了 webapp 風行的時代,前端業務邏輯越來越複雜,往往幾個 AJAX 請求之間互有依賴,有些請求依賴前面請求的數據,有些請求需要並行進行。還有在類似 node.js 的後端 JavaScript 環境中,因爲需要進行大量 IO 操作,問題更加明顯。這個時候使用回調函數來組織代碼往往會導致代碼難以閱讀。

現在比較流行的解決這個問題的方法是使用 Promise,可以將嵌套的回調函數展平。但是寫代碼和閱讀依然有額外的負擔。

另外一個方案是使用 ES6 中新增的 generator,因爲 generator 的本質是可以將一個函數執行暫停,並保存上下文,再次調用時恢復當時的狀態。co 模塊是個不錯的封裝。但是這樣略微有些濫用 generator 特性的感覺。

ES7 中有了更加標準的解決方案,新增了 async/await 兩個關鍵詞。async 可以聲明一個異步函數,此函數需要返回一個 Promise 對象。await可以等待一個 Promise 對象 resolve,並拿到結果。

比如下面的例子,以往我們無法在 JavaScript 中使用常見的 sleep 函數,只能使用 setTimeout 來註冊一個回調函數,在指定的時間之後再執行。有了 async/await 之後,我們就可以這樣實現了:

async function sleep(timeout) {
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      resolve();
    }, timeout);
  });
}

(async function() {
  console.log('Do some thing, ' + new Date());
  await sleep(3000);
  console.log('Do other things, ' + new Date());
})();

執行此段代碼,可以在終端中看到結果:

Do some thing, Mon Feb 23 2015 21:52:11 GMT+0800 (CST)
Do other things, Mon Feb 23 2015 21:52:14 GMT+0800 (CST)

另外 async 函數可以正常的返回結果和拋出異常。await 函數調用即可拿到結果,在外面包上 try/catch 就可以捕獲異常。下面是一個從豆瓣 API 獲取數據的例子:

var fetchDoubanApi = function() {
  return new Promise((resolve, reject) => {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          var response;
          try {
            response = JSON.parse(xhr.responseText);
          } catch (e) {
            reject(e);
          }
          if (response) {
            resolve(response, xhr.status, xhr);
          }
        } else {
          reject(xhr);
        }
      }
    };
    xhr.open('GET', 'https://api.douban.com/v2/user/aisk', true);
    xhr.setRequestHeader("Content-Type", "text/plain");
    xhr.send(data);
  });
};

(async function() {
  try {
    let result = await fetchDoubanApi();
    console.log(result);
  } catch (e) {
    console.log(e);
  }
})();

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