[譯]JavaScript Async/Await 秒殺 Promise 的6個理由(教學向)

原著:6 Reasons Why JavaScript's Async/Await Blows Promises Away (Tutorial)

作者:Mostafa Gaafar

譯者:Tingrui Li

先說一件事,Node自從7.6版本之后已經(jīng)原生支持async/await語法。如果你還未嘗試過它,這里有一些例子告訴你為何你應(yīng)當馬上開始使用它,并不再回頭。

Async/await 101

對于那些沒有聽說過這個玩意兒的人,這里有一些快速掃盲:

  • Async/await是編寫異步代碼新的方式,之前我們使用回調(diào)函數(shù)和Promise。
  • Async/await實際上是基于Promise的。它不能與常規(guī)回調(diào)函數(shù)和node回調(diào)函數(shù)一起使用。
  • Async/await跟Promise一樣,不會阻塞程序。
  • Async/await讓異步代碼看起來更像同步執(zhí)行的。這就是它強大的地方所在。

語法

假設(shè)一個方法 getJSON 返回一個Promise,這個Promise解析(resolve)出一個JSON對象,我們想要調(diào)用他并且打印那個JSON,返回"done"

你用Promise通常會這樣實現(xiàn):

const makeRequest = () =>
  getJSON()
    .then(data => {
      console.log(data)
      return "done"
    })

makeRequest()

而如果你用async/await:

const makeRequest = async () => {
  console.log(await getJSON())
  return "done"
}

makeRequest()

這里有一些區(qū)別:

  1. 我們的方法前面有一個關(guān)鍵字async。而await關(guān)鍵字只能被用于以async定義的方法內(nèi)部。任何async方法都隱式地返回一個Promise,并且解析(resolve)的值是你從方法內(nèi)return出來的東西(在這個例子中就是string "done")。

  2. 上面第一點表示我們不能在外層是用await,因為我們只能在async方法中使用它。

// 在外層不好使
// await makeRequest()

// 好使
makeRequest().then((result) => {
  // do something
})
  1. await getJSON()表示console.log會等到getJSON()這個Promise resolve之后才會打印它的值。

為什么這樣更好?

1. 簡潔、干凈

瞧瞧我們少寫了多少東西!就算是上面這么一個簡短的例子,我們都明顯少寫了很多代碼。不需要寫.then,創(chuàng)建異步函數(shù)去處理response,或者對我們根本用不到的變量起data這個名字。我們也避免了把代碼寫成很多層。在下面的例子中,將會凸顯這一點。

2. 錯誤處理

Async/await讓我們終于可以以同一種方法同時處理同步和異步錯誤:經(jīng)典的try/catch。在下面這個Promise的例子中,try/catch不會處理JSON.parse錯誤,因為那是在Promise中發(fā)生的。我們得在Promise中去.catch,然后重新寫一次我們的錯誤處理代碼,這可能比你的生產(chǎn)環(huán)境代碼中的那些console.log要冗余的多。

const makeRequest = () => {
  try {
    getJSON()
      .then(result => {
        // JSON.parse可能會失敗
        const data = JSON.parse(result)
        console.log(data)
      })
      // 處理JSON.parse可能拋出的錯誤
      .catch((err) => {
        console.log(err)
      })
  } catch (err) {
    console.log(err)
  }
}

現(xiàn)在我們用async/await寫這段代碼。catch代碼塊現(xiàn)在會處理JSON轉(zhuǎn)換錯誤了。

const makeRequest = async () => {
  try {
    // JSON.parse可能會失敗
    const data = JSON.parse(await getJSON())
    console.log(data)
  } catch (err) {
    console.log(err)
  }
}

是不是好棒棒?

3. 條件判斷

想象一下這個場景,一段代碼需要獲取一些數(shù)據(jù)然后決定是返回這些數(shù)據(jù),還是根據(jù)這些數(shù)據(jù)中的一些值來獲取更多的數(shù)據(jù)。

const makeRequest = () => {
  return getJSON()
    .then(data => {
      if (data.needsAnotherRequest) {
        return makeAnotherRequest(data)
          .then(moreData => {
            console.log(moreData)
            return moreData
          })
      } else {
        console.log(data)
        return data
      }
    })
}

是不是看著就頭大?這六層的代碼很容易讓你頭暈?zāi)垦!D切├ㄌ枺约皉eturn語句只是為了將最終結(jié)果傳遞到主要Promise中。

這個例子用async/await重寫以后可讀性大大提高。

const makeRequest = async () => {
  const data = await getJSON()
  if (data.needsAnotherRequest) {
    const moreData = await makeAnotherRequest(data);
    console.log(moreData)
    return moreData
  } else {
    console.log(data)
    return data    
  }
}

4. 中間值

你可能遇到這種情況:你需要調(diào)用promise1然后用它的返回值去調(diào)用promise2,然后用兩者的返回值去調(diào)用promise3,你的代碼很可能是這樣的:

const makeRequest = () => {
  return promise1()
    .then(value1 => {
      // do something
      return promise2(value1)
        .then(value2 => {
          // do something          
          return promise3(value1, value2)
        })
    })
}

如果promise3不需要value1,這個層級關(guān)系會清楚一些。如果你不能忍受,你可以用promise.all包裝value1和value2:

const makeRequest = () => {
  return promise1()
    .then(value1 => {
      // do something
      return Promise.all([value1, promise2(value1)])
    })
    .then(([value1, value2]) => {
      // do something          
      return promise3(value1, value2)
    })
}

這種寫法犧牲了可讀性,value1value2不應(yīng)當出于任何理由屬于同一個數(shù)組。

同樣的邏輯如果使用async/await來寫會變得很簡單,會讓你懷疑以前為什么掙扎著讓使用Promise的代碼看起來更簡單:

const makeRequest = async () => {
  const value1 = await promise1()
  const value2 = await promise2(value1)
  return promise3(value1, value2)
}

5. 錯誤棧

想像這樣一個情景:連續(xù)鏈式調(diào)用多個Promise,然后其中某個地方可能會拋出異常:

const makeRequest = () => {
  return callAPromise()
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => callAPromise())
    .then(() => {
      throw new Error("oops");
    })
}

makeRequest()
  .catch(err => {
    console.log(err);
    // 輸出
    // Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
  })

這個錯誤輸出棧不能明確的指示錯誤發(fā)生在哪里。甚至會產(chǎn)生誤導(dǎo):整個錯誤只包含一個方法名then

然而,async/await的錯誤棧會指向具體產(chǎn)生錯誤的方法:

const makeRequest = async () => {
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  throw new Error("oops");
}

makeRequest()
  .catch(err => {
    console.log(err);
    // output
    // Error: oops at makeRequest (index.js:7:9)
  })

其實當你本地開發(fā)調(diào)試時,這一點意義不大。但是如果你需要在生產(chǎn)環(huán)境服務(wù)器上的代碼上找錯時,就非常實用了。在這種情況下,知道錯誤發(fā)生在makeRequest比知道錯誤發(fā)生在then.then.then.then...要好很多......

6. 調(diào)試

最后,async/await一個巨大的優(yōu)勢是它非常容易調(diào)試。對Promise進行調(diào)試出于兩個原因非常的痛苦:

  1. 你不能在返回表達式的箭頭函數(shù)上設(shè)置斷點(沒有函數(shù)體)。
const makeRequest = () =>{
reutrn callAPromise()
.then(()=>callAPromise())
.then(()=>callAPromise())
.then(()=>callAPromise())
.then(()=>callAPromise())
}
  1. 如果你在一個.then塊內(nèi)設(shè)置了斷點,然后用step-over等功能時,調(diào)試器不會進入.then代碼因為他只會"step"進同步代碼。

使用async/await你不需要那么多箭頭函數(shù),你可以直接"step"那些await調(diào)用,就像普通的同步調(diào)用一樣。

const makeRequest = async () =>{
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
  await callAPromise()
}

結(jié)論

Async/await是Javascript近幾年來最具有革命性的特性之一,它讓你體會到Promise是多么的混亂,然后給你一個方便的替代品。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內(nèi)容