簡介
很久以前绍赛,在Promise出現(xiàn)之前,編寫js的異步代碼是一件十分蛋疼的事情辑畦,一旦異步流程一復(fù)雜吗蚌,回調(diào)地獄就會(huì)等著你,所以基于node.js的服務(wù)端編碼開發(fā)的體驗(yàn)非常差纯出,項(xiàng)目的可維護(hù)性和可讀性也非常差蚯妇,這也是最初node.js最為人所詬病的地方。
在現(xiàn)代的js項(xiàng)目中暂筝,回調(diào)函數(shù)已經(jīng)基本被Promise取代箩言,而async/await的出現(xiàn)更是解決的promise在使用中會(huì)產(chǎn)生大量的膠水代碼,以及不同Promise之間共享數(shù)據(jù)困難的問題焕襟,這又大大提升的編寫異步流程的開發(fā)體驗(yàn)陨收,但是async/await本身是基于promise的,而在很多情況下鸵赖,async/await并不能完全取代promise的位置务漩,理解promise是編寫高質(zhì)量的異步代碼的基礎(chǔ)。下面我們就來探討一下在js中的異步流程控制的問題卫漫。如何在異步代碼中保證異步流程的執(zhí)行效率以及可讀性和可維護(hù)性菲饼。以下為一個(gè)例子:
如何制作一個(gè)披薩?
制作餅皮(dough)
制作醬汁(sauce)
品嘗一下醬汁列赎,根據(jù)醬汁的口味決定放哪種芝士(cheese)
第一版實(shí)現(xiàn)
async function makePizza(sauceType = 'red') {
let dough = await makeDough();
let sauce = await makeSauce(sauceType);
let cheese = await grateCheese(sauce.determineCheese());
dough.add(sauce);
dough.add(cheese);
return dough;
}
基于async/await宏悦,看起來邏輯很清晰镐确,但是它最大的問題是這不是一個(gè)最優(yōu)的異步流程控制。
第二版實(shí)現(xiàn)
// 原來的流程
|-------- dough --------> |-------- sauce --------> |-- cheese -->
// 改進(jìn)的流程
|-------- dough -------->
|-------- sauce --------> |-- cheese -->
async function makePizza(sauceType = 'red') {
let [ dough, sauce ] =
await Promise.all([ makeDough(), makeSauce(sauceType) ]);
let cheese = await grateCheese(sauce.determineCheese());
dough.add(sauce);
dough.add(cheese);
return dough;
}
效率比第一版就高了很多饼煞。使用promise.all并行執(zhí)行異步任務(wù)源葫,流程也比較清晰。
第三版實(shí)現(xiàn)
由于制作dough的時(shí)間是比較長的砖瞧,我們的流程其實(shí)還有改進(jìn)的空間息堂,如下。
// 上一步改進(jìn)的流程
|-------- dough -------->
|--- sauce ---> |-- cheese -->
// 進(jìn)一步改進(jìn)的流程
|--------- dough --------->
|---- sauce ----> |-- cheese -->
function makePizza(sauceType = 'red') {
let doughPromise = makeDough();
let saucePromise = makeSauce(sauceType);
let cheesePromise = saucePromise.then(sauce => {
return grateCheese(sauce.determineCheese());
});
return Promise.all([ doughPromise, saucePromise, cheesePromise ])
.then(([ dough, sauce, cheese ]) => {
dough.add(sauce);
dough.add(cheese);
return dough;
});
}
現(xiàn)在的異步流程應(yīng)該是最優(yōu)的了块促,但是問題來了荣堰,這樣的流程用async/await表示的話就有點(diǎn)麻煩了,我們這里先用promise實(shí)現(xiàn)了這個(gè)最優(yōu)流程竭翠,可以看到promise的弊端很明顯振坚,充滿了膠水代碼,我們?cè)僖膊荒芟裎覀兊牡谝话鎸?shí)現(xiàn)一樣斋扰,一眼就能看出我們這個(gè)函數(shù)的異步流程是怎么樣的了渡八,可讀性下降了。
第四版實(shí)現(xiàn)
// 最優(yōu)流程
|--------- dough --------->
|---- sauce ----> |-- cheese -->
async function makePizza(sauceType = 'red') {
let doughPromise = makeDough();
let saucePromise = makeSauce(sauceType);
let sauce = await saucePromise;
let cheese = await grateCheese(sauce.determineCheese());
let dough = await doughPromise;
dough.add(sauce);
dough.add(cheese);
return dough;
}
這一版實(shí)現(xiàn)传货,代碼看起來整潔了很多屎鳍,但是它的異步流程更加不清晰了,通過提前創(chuàng)建promise然后在之后再進(jìn)行await问裕,我們得到了整潔的代碼逮壁,但是卻使得流程更加不清晰,隱式的執(zhí)行promise然后await僻澎,讓await的語義變得很不明了貌踏。
第五版實(shí)現(xiàn)
async function makePizza(sauceType = 'red') {
let prepareDough = memoize(async () => makeDough());
let prepareSauce = memoize(async () => makeSauce(sauceType));
let prepareCheese = memoize(async () => {
return grateCheese((await prepareSauce()).determineCheese());
});
let [ dough, sauce, cheese ] =
await Promise.all([
prepareDough(), prepareSauce(), prepareCheese()
]);
dough.add(sauce);
dough.add(cheese);
return dough;
}
這個(gè)版本相比于上一個(gè)版本的區(qū)別主要是現(xiàn)在我們不再提前創(chuàng)建promise十饥,而保存為一個(gè)生成函數(shù)窟勃,并且使用memoize緩存結(jié)果,從而使得promise只需要resolve一次就可以緩存結(jié)果逗堵。這樣子的異步流程相比于上一個(gè)版本就清楚了很多秉氧,而代碼依然可以保持很好的可讀性。
總結(jié)
感覺Promise.all和await/async的結(jié)合依然不是很優(yōu)雅蜒秤,總感覺有點(diǎn)難受汁咏,在未來,js的異步流程控制肯定還會(huì)有更優(yōu)雅的解決方案作媚。