原文鏈接:小哥哥小姐姐,來嘗嘗 Async 函數(shù)這塊語法糖
ES7(ECMAScript 2016)推出了Async函數(shù)(async/await),實(shí)現(xiàn)了以順序、同步代碼的編寫方式來控制異步流程,徹底解決了困擾JavaScript開發(fā)者的“回調(diào)地獄”問題。
const result = [];
// pseudo-code, ajax stand for an asynchronous request
ajax('url1', function(err, data){
if(err) {...}
result.push(data)
ajax('url2', function(err, data){
if(err) {...}
result.push(data)
})
})
console.log(result)
現(xiàn)在可以寫成如下同步代碼的樣式了:
async function example() {
const r1 = await new Promise(resolve =>
setTimeout(resolve, 500, 'slowest')
)
const r2 = await new Promise(resolve =>
setTimeout(resolve, 200, 'slow')
)
return [r1, r2]
}
example().then(result => console.log(result))
// ['slowest', 'slow']
Async函數(shù)需要在function前面添加async關(guān)鍵字,同時內(nèi)部以await關(guān)鍵字來“阻塞”異步操作,直到異步操作返回結(jié)果,然后再繼續(xù)執(zhí)行。
當(dāng)前JavaScript編程主要是異步編程
- 當(dāng)前JavaScript編程主要是異步編程。為什么這么說呢?網(wǎng)頁或Web開發(fā)最早從2005年Ajax流行開始,逐步向重交互時代邁進(jìn)。
- 特別是SPA(Single Page Application,單頁應(yīng)用)流行之后,一度有人提出“Web頁面要轉(zhuǎn)向Web應(yīng)用,而且要媲美原生應(yīng)用”。
- 如今在前端開發(fā)組件化的背景下催生的Angular、React和Vue,都是SPA進(jìn)一步演化的結(jié)果。
- 頁面在首次加載過程中,與JavaScript相關(guān)的主要任務(wù)就是加載基礎(chǔ)運(yùn)行庫和擴(kuò)展庫(包括給低版本瀏覽器打補(bǔ)丁的腳本),然后初始化和設(shè)置頁面的狀態(tài)。
- 目前JavaScript編程最大的應(yīng)用是Web交互,而Web交互的核心就是異步邏輯。
ES6之前JavaScript中控制異步流程的手段只有事件和回調(diào)。比如下面的示例展示了通過原生XMLHttpRequest對象發(fā)送異步請求,然后給onload和onerror事件分別注冊成功和錯誤處理函數(shù):
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function () {
if (req.status == 200) {
processData(req.response);
}
};
req.onerror = function () {
console.log('Network Error');
};
req.send();
事件和回調(diào)有很多問題,主要是它們只適用于簡單的情況。邏輯一復(fù)雜,代碼的編寫和維護(hù)成本就成倍上升。比如,大家熟知的“回調(diào)地獄”。更重要的是,回調(diào)模式的異步本質(zhì)與人類同步、順序的思維模式是相悖的。
為了應(yīng)對越來越復(fù)雜的異步編程需求,ES6推出了解決上述問題的Promise。
Promise
Promise,人們普遍的理解就是:“Promise是一個未來值的占位符”。也就是說,從語義上講,一個Promise對象代表一個對未來值的“承諾”(promise),這個承諾將來如果“兌現(xiàn)”(fulfill),就會“解決”(resolve)為一個有意義的數(shù)據(jù);如果“拒絕”(reject),就會“解決”為一個“拒絕理由”(rejection reason),就是一個錯誤消息。
Promise對象的狀態(tài)很簡單,一生下來的狀態(tài)是pending(待定),將來兌現(xiàn)了,狀態(tài)變成fulfilled;拒絕了,狀態(tài)變成rejected。fulfilled和rejected顯然是一種“確定”(settled)狀態(tài)。以上狀態(tài)轉(zhuǎn)換是不可逆的

以下是通過Prmoise(executor)構(gòu)造函數(shù)創(chuàng)建Promise實(shí)例的詳細(xì)過程:要傳入一個“執(zhí)行函數(shù)”(executor),這個執(zhí)行函數(shù)又接收兩個參數(shù)“解決函數(shù)”(resolver)和“拒絕函數(shù)”(rejector),代碼中分別對應(yīng)變量resolve和reject,作用分別是將新建對象的狀態(tài)由pending改為fulfilled和rejected,同時返回“兌現(xiàn)值”(fulfillment)和“拒絕理由”(rejection)。當(dāng)然,resolve和reject都是在異步操作的回調(diào)中調(diào)用的。調(diào)用之后,運(yùn)行時環(huán)境(瀏覽器引擎或Node.js的libuv)中的事件循環(huán)調(diào)度機(jī)制會把與之相關(guān)的反應(yīng)函數(shù)——兌現(xiàn)反應(yīng)函數(shù)或拒絕反應(yīng)函數(shù)以及相關(guān)的參數(shù)添加到“微任務(wù)”隊(duì)列,以便下一次“循檢”(tick)時調(diào)度到JavaScript線程去執(zhí)行。

如前所述,Promise對象的狀態(tài)由pending變成fulfilled,就會執(zhí)行“兌現(xiàn)反應(yīng)函數(shù)”(fulfillment reaction);而變成rejected,就會執(zhí)行“拒絕反應(yīng)函數(shù)”(rejection reaction)。如下例所示,常規(guī)的方式是通過p.then()注冊兌現(xiàn)函數(shù),通過p.catch()注冊拒絕函數(shù):
p.then(res => { // 兌現(xiàn)反應(yīng)函數(shù)
// res === 'random success'
})
p.catch(err => { // 拒絕反應(yīng)函數(shù)
// err === 'random failure'
})
當(dāng)然還有非常規(guī)的方式,而且有時候非常規(guī)方式可能更好用:
// 通過一個.then()方法同時注冊兌現(xiàn)和拒絕函數(shù)
p.then(
res => {
// handle response
},
err => {
// handle error
}
)
// 通過.then()方法只注冊一個函數(shù):兌現(xiàn)函數(shù)
p.then(res => {
// handle response
})
// 通過.then()方法只傳入拒絕函數(shù),兌現(xiàn)函數(shù)的位置傳null
p.then(null, err => {
// handle error
})
關(guān)于Promise就這樣吧。ES6除了Promise,還推出了Iterator(迭代器)和Generator(生成器),于是就有成就Async函數(shù)的PIG組合。(詳見原文鏈接)