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);
});