基于Promise和Async/Await的異步流程控制

簡介

很久以前,在Promise出現(xiàn)之前,編寫js的異步代碼是一件十分蛋疼的事情,一旦異步流程一復(fù)雜,回調(diào)地獄就會等著你,所以基于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在使用中會產(chǎn)生大量的膠水代碼,以及不同Promise之間共享數(shù)據(jù)困難的問題,這又大大提升的編寫異步流程的開發(fā)體驗(yàn),但是async/await本身是基于promise的,而在很多情況下,async/await并不能完全取代promise的位置,理解promise是編寫高質(zhì)量的異步代碼的基礎(chǔ)。下面我們就來探討一下在js中的異步流程控制的問題。如何在異步代碼中保證異步流程的執(zhí)行效率以及可讀性和可維護(hù)性。以下為一個(gè)例子:

如何制作一個(gè)披薩?

  1. 制作餅皮(dough)

  2. 制作醬汁(sauce)

  3. 品嘗一下醬汁,根據(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的弊端很明顯,充滿了膠水代碼,我們再也不能像我們的第一版實(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的異步流程控制肯定還會有更優(yōu)雅的解決方案。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 異步編程對JavaScript語言太重要。Javascript語言的執(zhí)行環(huán)境是“單線程”的,如果沒有異步編程,根本...
    呼呼哥閱讀 7,400評論 5 22
  • 一.非阻塞和異步 借用知乎用戶嚴(yán)肅的回答在此總結(jié)下,同步和異步是針對消息通信機(jī)制,同步代表一個(gè)client發(fā)出一個(gè)...
    Daniel_adu閱讀 1,922評論 0 8
  • javascript的運(yùn)行機(jī)制是單線程處理,即只有上一個(gè)任務(wù)完成后,才會執(zhí)行下一個(gè)任務(wù),這種機(jī)制也被稱為“同步”。...
    我是xy閱讀 3,992評論 1 6
  • 單線程與異步 Javascript是單線程運(yùn)行、支持異步機(jī)制的語言。進(jìn)入正題之前,我們有必要先理解這種運(yùn)行方式。 ...
    貝聊科技閱讀 653評論 0 0
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點(diǎn)點(diǎn)福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運(yùn)大...
    HetfieldJoe閱讀 11,120評論 26 95

友情鏈接更多精彩內(nèi)容