前言
async 函數(shù)是目前解決JS異步編程的終極解決方案,學(xué)習(xí)它之前你可以先看看我寫的我對(duì)Promises的理解和理解 ES6 Generator 函數(shù),因?yàn)閍sync/await需要聯(lián)合Promises使用,而且是Generator函數(shù)的語法糖,所以必須先學(xué)Promises和Generator,最少,需要先學(xué)Promises。
async函數(shù)在較低版本的Chrome瀏覽器中,需要開啟“實(shí)驗(yàn)性 JavaScript”才能使用,55版本以上默認(rèn)支持。
例1:a/a的最簡(jiǎn)單舉例
一個(gè)最簡(jiǎn)單的使用a/a的范例如下:
function type(delayedTime) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(delayedTime);
resolve();
}, delayedTime);
});
}
async function asyncFunc() {
await type(3000);
await type(2500);
console.log(1);
};
asyncFunc();
最終執(zhí)行結(jié)果是:等三秒,打印3000,然后等2.5秒,打印2500,然后緊跟輸出1。
一、先看type函數(shù),他是一個(gè)普通函數(shù),內(nèi)部返回一個(gè)Promise對(duì)象。除此之外沒有特別的。
二、然后看asyncFunc函數(shù),它不是普通函數(shù),它前面有async關(guān)鍵字,這意味著asyncFunc函數(shù)是一個(gè)異步函數(shù),async就是異步的意思,很好理解,比Promise(允諾)和Generator(生成器)要好理解的多。
三、看async函數(shù)的內(nèi)部,async函數(shù)的內(nèi)部一定會(huì)有await關(guān)鍵字,await也容易理解,就是等待。也就是說,等await后面的表達(dá)式執(zhí)行完了,才執(zhí)行下一個(gè)表達(dá)式。
最后可以看出,雖然我說你必須先學(xué)Promises和Generator,但是這不意味著a/a最難學(xué),相反,它才是最好學(xué)的,因?yàn)樗耆系谝惶鞂W(xué)習(xí)JS的初學(xué)者對(duì)JS的錯(cuò)誤理解:
“我寫JS只需要從上到下一行一行的寫下去,代碼就肯定會(huì)是一行一行的從上到下執(zhí)行下去,每一句都會(huì)等上一句執(zhí)行完了才執(zhí)行?!?/p>
雖然這種理解絕對(duì)是錯(cuò)誤的認(rèn)知,但是,其實(shí),這才是異步編程的最理想解決方案:“你根本看不出它是異步代碼,你只需要像寫同步代碼一樣寫異步代碼就好了?!?/p>
總結(jié)起來,a/a大法的最基本用法就是:
1. 首先定義一個(gè)或多個(gè)普通函數(shù),函數(shù)必須返回的是Promise對(duì)象(事實(shí)上你可以返回其他數(shù)據(jù),但這就失去了a/a的威力)。Promise對(duì)象中可以寫任意異步語句,必須有resolve()
。
2. 然后定義一個(gè)async函數(shù),函數(shù)語句就是執(zhí)行那些個(gè)普通函數(shù),注意,每個(gè)執(zhí)行語句前面都加上await
關(guān)鍵字。async
表示函數(shù)里有異步操作,await
表示緊跟在后面的表達(dá)式需要等待結(jié)果。(await
后面也可以跟原始類型,但沒意義。)
3. 執(zhí)行這個(gè)async函數(shù)即可。async函數(shù)的返回值是Promise對(duì)象,你可以用Promise對(duì)象的.then()方法指定下一步的操作。
例2:把例1用Generator函數(shù)寫法寫出來
function type(delayedTime) {
setTimeout(function () {
console.log(delayedTime);
it.next();
}, delayedTime);
}
function* asyncFunc() {
yield type(3000);
yield type(2500);
console.log(1);
}
var it = asyncFunc();
it.next();
可以看出a/a跟Generator函數(shù)的區(qū)別:
Generator函數(shù)的執(zhí)行必須靠執(zhí)行器(例子中是
it.next()
),每一個(gè)普通函數(shù)中通常會(huì)有一個(gè)執(zhí)行器,多個(gè)普通函數(shù)中有多個(gè)執(zhí)行器,實(shí)踐中如果偶爾忘了寫執(zhí)行器,那么整個(gè)代碼就不會(huì)正常執(zhí)行;而async函數(shù)自帶執(zhí)行器。也就是說,async函數(shù)的執(zhí)行,與普通函數(shù)一模一樣,只要一行asyncFunc()
。async和await,比起星號(hào)和yield,語義更清楚。
async函數(shù)的返回值是Promise對(duì)象,這比Generator函數(shù)的返回值是迭代器對(duì)象方便得多。
例3:把例1用Promises方式寫出來
function type(delayedTime) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(delayedTime);
resolve();
}, delayedTime);
});
}
type(3000)
.then(function() {
return type(2500);
})
.then(function(){
console.log(1);
});
可以看出a/a與Promises寫法的區(qū)別:
Promises寫法每一步要為下一步的.then返回一個(gè)Promise對(duì)象,注意,
return type(1000);
的return
不要忘了寫,不要以為函數(shù)里有return,這里就可以省略。而a/a寫法,在async函數(shù)中不需要有return關(guān)鍵字。Promises寫法每一個(gè)
.then
都要接收一個(gè)回調(diào)函數(shù)作為參數(shù),而a/a完全不用這么麻煩,await等待的雖然是promise對(duì)象,但不必寫.then(..)
,直接可以得到返回值。所以說await是then的語法糖。當(dāng)然了,async函數(shù)執(zhí)行之后,也返回一個(gè)Promise對(duì)象,也就是說,不光是async函數(shù)內(nèi)部的await有then的作用,而且async函數(shù)本身也可以連綴then,等于肚子里可以連綴then,肚子外還可以連綴then。
async函數(shù)連綴.then的最基本寫法
上面說了,async函數(shù)永遠(yuǎn)返回一個(gè)Promise對(duì)象,所以它當(dāng)然可以連綴.then方法。
復(fù)習(xí)一下剛才的命題,3秒之后打印3000
,然后2.5秒之后打印2500
,然后立即打印1
,對(duì)吧?現(xiàn)在我打算增加后續(xù):
打印1
之后,再等2秒,打印2000
,再等1.5秒,打印1500
。
代碼如下:
function type(delayedTime) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(delayedTime);
resolve(delayedTime);
}, delayedTime);
});
}
async function asyncFunc() {
await type(3000);
await type(2500);
console.log(1);
};
asyncFunc()
.then(function () {
return type(2000);
})
.then(function () {
return type(1500);
});
關(guān)鍵點(diǎn)在于return
關(guān)鍵字,它用于返回一個(gè)Promise對(duì)象。
async函數(shù)內(nèi)部await傳遞數(shù)據(jù)的寫法
現(xiàn)在的命題是,我想要先延遲3秒,打印3000
,然后把3000這個(gè)數(shù)字減去500(也就是得到2500),作為下一個(gè)延遲的毫秒數(shù),然后打印這個(gè)毫秒數(shù),然后繼續(xù)減去500(也就是得到2000),作為再下一個(gè)延遲的毫秒數(shù),然后打印這個(gè)毫秒數(shù),然后再繼續(xù)減去500(也就是得到1500),然后打印這個(gè)毫秒數(shù)。
總共打印4個(gè)數(shù)。寫法如下:
function type(delayedTime) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(delayedTime);
resolve(delayedTime);
}, delayedTime);
});
}
async function asyncFunc() {
var t1 = await type(3000);
var t2 = await type(t1 - 500);
var t3 = await type(t2 - 500);
type(t3 - 500);
};
asyncFunc();
關(guān)鍵點(diǎn)有2個(gè),一個(gè)是resolve(delayedTime)
,它用于發(fā)出數(shù)據(jù),一個(gè)是var t1 = await type(3000);
,這里注意,type(3000)
返回的是Promise對(duì)象,await返回的是delayedTime的值。
我們這個(gè)例子是每次減500,甚至可以使用for循環(huán)來執(zhí)行,并不會(huì)打亂順序。
async函數(shù)外部連綴then并且還要傳遞數(shù)據(jù)的寫法
除了內(nèi)部能傳遞數(shù)據(jù),外部也一樣可以。代碼如下:
function type(delayedTime) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(delayedTime);
resolve(delayedTime);
}, delayedTime);
});
}
async function asyncFunc() {
return await type(3000);
}
asyncFunc()
.then(function (result) {
return type(result - 500);
})
.then(function (result) {
return type(result - 500);
})
.then(function (result) {
type(result - 500);
});
可以看出,外部連綴then的寫法,代碼比較冗余,所以盡量在內(nèi)部使用await實(shí)現(xiàn)。因?yàn)閍wait已經(jīng)有then的作用,沒必要在外部連綴then。
async函數(shù)有多種使用形式
函數(shù)聲明形式,也是最基本的形式
async function foo() {}
函數(shù)表達(dá)式形式
const foo = async function () {};
對(duì)象的方法形式
let obj = { async foo() {} };
obj.foo().then(...)
Class的方法
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jake').then(…);
箭頭函數(shù)
const foo = async () => {};
錯(cuò)誤處理
在不考慮出錯(cuò)的情況下,async/await就以上這么點(diǎn)東西,其實(shí)就是async、await、return這三個(gè)關(guān)鍵字各種倒騰,每一步都要返回Promise對(duì)象就行了。
下面考慮錯(cuò)誤處理。
首先我還是假設(shè)你已經(jīng)懂了Promises的錯(cuò)誤處理機(jī)制。復(fù)習(xí)一下:
Promises利用
reject()
來傳遞錯(cuò)誤,利用.then的第二個(gè)回調(diào)函數(shù),或利用.catch來捕獲錯(cuò)誤。
a/a也是如此,舉個(gè)栗子:
function f() {
return new Promise(function (resolve, reject) {
reject('出錯(cuò)了');
});
}
async function asyncFunc() {
await f();
};
asyncFunc().then(function (e) {
console.log(1111);
}, function (e) {
console.log(e); // 出錯(cuò)了
});
asyncFunc().catch(function (e) {
console.log(e); // 出錯(cuò)了
});
之前說過,await能夠起到.then的作用,await如果等待到的是失敗的結(jié)果,就必須先處理這個(gè)結(jié)果,才能進(jìn)入下一個(gè)流程。這一點(diǎn)很重要。
比如,上面例子如果我成這樣:
async function asyncFunc() {
await f();
await f();
};
第一個(gè)f()的執(zhí)行,只會(huì)得到失敗的結(jié)果,這個(gè)結(jié)果如果不被處理,那么第二個(gè)f()根本不會(huì)執(zhí)行。驗(yàn)證一下:
function f(data) {
return new Promise(function (resolve, reject) {
reject(data);
});
}
async function asyncFunc() {
await f('data1'); // await發(fā)現(xiàn)這個(gè)Promise的錯(cuò)誤沒有得到處理,所以不會(huì)進(jìn)入下個(gè)環(huán)節(jié)
await f('data2'); // 那么這一步就不執(zhí)行
};
asyncFunc().catch(function (e) {
console.log(e); // data1 也就是只會(huì)捕獲到第一個(gè)環(huán)節(jié)的錯(cuò)誤
});
現(xiàn)在我略加改動(dòng),給f('data1')
做失敗處理,你再看看:
function f(data) {
return new Promise(function (resolve, reject) {
reject(data);
});
}
async function asyncFunc() {
await f('data1').catch(function (e) {
console.log(e); // data1
});
await f('data2');
};
asyncFunc().catch(function (e) {
console.log(e); // data2
});
所以你可以看到,async內(nèi)部也可以有.then和.catch級(jí)聯(lián),外部也可以繼續(xù)級(jí)聯(lián),a/a的宇宙,就是Promise的狀態(tài)和數(shù)據(jù)不斷傳遞的宇宙。a/a就是Promises的超集。
同時(shí)我們也可以看到,async內(nèi)部和外部都可以有.then和.catch級(jí)聯(lián),所以你就要思考到底是在內(nèi)部寫流程,還是外部寫流程,怎樣才能寫出最清晰的代碼。我個(gè)人建議優(yōu)先考慮在內(nèi)部寫小流程,在外部寫大流程。
談?wù)則ry {} catch(e) {}
你會(huì)在一些參考文章里看到try {} catch(e) {}
的用法,它也用于在async函數(shù)內(nèi)部實(shí)現(xiàn)錯(cuò)誤捕獲。就比如下面這種:
function f(data) {
return new Promise(function (resolve, reject) {
reject(data);
});
}
async function asyncFunc() {
try {
await f('data1');
} catch(e) {
console.log(e); // data1
}
await f('data2');
};
asyncFunc().catch(function (e) {
console.log(e); // data2
});
也就是說,既然你認(rèn)為第一步可能出錯(cuò),那么就try一下,這個(gè)寫法當(dāng)然是可以運(yùn)行的,但是盡量不要這么做,因?yàn)樵赼/a領(lǐng)域,已經(jīng)有.then和.catch來負(fù)責(zé)錯(cuò)誤分支,就根本沒必要引入try {} catch(e) {}
的寫法,這種寫法徒增代碼復(fù)雜度。
a/a與Promises特有方法的配合
了解Promises規(guī)范(可以參看我的http://www.lxweimin.com/p/b497eab58ed7),就會(huì)知道Promise.all()和Promise.race(),這兩個(gè)特有方法的用法見我寫的文檔,跟a/a的結(jié)合使用也很簡(jiǎn)單,比如Promise.all():
// 例1,有一個(gè)失敗就進(jìn)入下一個(gè)環(huán)節(jié)
function f(data) {
return new Promise(function (resolve, reject) {
reject(data);
});
}
async function asyncF() {
await Promise.all([f('data1'),f('data2')]).catch(function (e) {
console.log(e); // data1
});
};
asyncF();
// 例2,全部成功才進(jìn)入下一個(gè)環(huán)節(jié)
function g(data) {
return new Promise(function (resolve, reject) {
resolve(data);
});
}
async function asyncG() {
await Promise.all([g('data1'),g('data2')]).then(function (e) {
console.log(e); // ["data1", "data2"]
});
};
asyncG();
Promise.race()跟a/a的結(jié)合也很簡(jiǎn)單,本質(zhì)都是Promise對(duì)象的狀態(tài)變化和數(shù)據(jù)傳遞,不多說。