ES6 Async/Await 完爆Promise的6個(gè)原因

自從Node的7.6版本,已經(jīng)默認(rèn)支持async/await特性了逆趣。如果你還沒有使用過他蝶溶,或者對他的用法不太了解,這篇文章會(huì)告訴你為什么這個(gè)特性“不容錯(cuò)過”宣渗。本文輔以大量實(shí)例抖所,相信你能很輕松的看懂,并了解Javascript處理異步的一大殺器痕囱。

文章靈感和內(nèi)容借鑒了6 Reasons Why JavaScript’s Async/Await Blows Promises Away (Tutorial)田轧,英文好的同學(xué)可以直接戳原版參考。

初識(shí)Async/await

對于還不了解Async/await特性的同學(xué)鞍恢,下面一段是一個(gè)“速成”培訓(xùn)傻粘。
Async/await 是Javascript編寫異步程序的新方法。以往的異步方法無外乎回調(diào)函數(shù)和Promise帮掉。但是Async/await建立于Promise之上弦悉。對于Javascript處理異步,是個(gè)老生常談卻歷久彌新的話題:

從最早的回調(diào)函數(shù)旭寿,到 Promise 對象警绩,再到 Generator 函數(shù),每次都有所改進(jìn)盅称,但又讓人覺得不徹底肩祥。它們都有額外的復(fù)雜性,都需要理解抽象的底層運(yùn)行機(jī)制缩膝。
異步編程的最高境界混狠,就是根本不用關(guān)心它是不是異步。
async 函數(shù)就是隧道盡頭的亮光疾层,很多人認(rèn)為它是異步操作的終極解決方案将饺。

Async/await語法

試想一下,我們有一個(gè)getJSON方法,該方法發(fā)送一個(gè)異步請求JSON數(shù)據(jù)予弧,并返回一個(gè)promise對象刮吧。這個(gè)promise對象的resolve方法傳遞異步獲得的JSON數(shù)據(jù)。具體例子的使用如下:

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

makeRequest()

在使用async/await時(shí)掖蛤,寫法如下:

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

makeRequest()

對比兩種寫法杀捻,針對第二種,我需要進(jìn)一步說明:

1)第二種寫法(使用async/await)蚓庭,在主體函數(shù)之前使用了async關(guān)鍵字致讥。在函數(shù)體內(nèi),使用了await關(guān)鍵字器赞。當(dāng)然await關(guān)鍵字只能出現(xiàn)在用async聲明的函數(shù)體內(nèi)垢袱。該函數(shù)會(huì)隱式地返回一個(gè)Promise對象,函數(shù)體內(nèi)的return值港柜,將會(huì)作為這個(gè)Promise對象resolve時(shí)的參數(shù)请契。
可以使用then方法添加回調(diào)函數(shù)。當(dāng)函數(shù)執(zhí)行的時(shí)候潘懊,一旦遇到await就會(huì)先返回姚糊,等到異步操作完成贿衍,再接著執(zhí)行函數(shù)體內(nèi)后面的語句授舟。

2)示例中,await getJSON() 說明console.log的調(diào)用贸辈,會(huì)等到getJSON()返回的promise對象resolve之后觸發(fā)释树。

我們在看一個(gè)例子加強(qiáng)一下理解,該例子取自阮一峰大神的《ECMAScript 6 入門》一書:

function timeout(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

async function asyncPrint(value, ms) {
    await timeout(ms);
    console.log(value);
}

asyncPrint('hello world', 50);

上面代碼指定50毫秒以后擎淤,輸出hello world奢啥。

Async/await究竟好在哪里?

那么嘴拢,同樣是處理異步操作桩盲,Async/await究竟好在哪里呢?
我們總結(jié)出以下6點(diǎn)席吴。

簡約而干凈Concise and clean

我們看一下上面兩處代碼的代碼量九串,就可以直觀地看出使用Async/await對于代碼量的節(jié)省是很明顯的堡掏。對比Promise,我們不需要書寫.then,不需要新建一個(gè)匿名函數(shù)處理響應(yīng)涯呻,也不需要再把數(shù)據(jù)賦值給一個(gè)我們其實(shí)并不需要的變量。同樣文留,我們避免了耦合的出現(xiàn)题禀。這些看似很小的優(yōu)勢其實(shí)是很直觀的,在下面的代碼示例中,將會(huì)更加放大撕捍。

錯(cuò)誤處理Error handling

Async/await使得處理同步+異步錯(cuò)誤成為了現(xiàn)實(shí)拿穴。我們同樣使用try/catch結(jié)構(gòu),但是在promises的情況下忧风,try/catch難以處理在JSON.parse過程中的問題贞言,原因是這個(gè)錯(cuò)誤發(fā)生在Promise內(nèi)部。想要處理這種情況下的錯(cuò)誤阀蒂,我們只能再嵌套一層try/catch该窗,就像這樣:

const makeRequest = () => {
    try {
    getJSON()
        .then(result => {
            // this parse may fail
            const data = JSON.parse(result)
            console.log(data)
        })
        // uncomment this block to handle asynchronous errors
        // .catch((err) => {
        //   console.log(err)
        // })
        } 
    catch (err) {
        console.log(err)
    }
}

但是,如果用async/await處理蚤霞,一切變得簡單酗失,解析中的錯(cuò)誤也能輕而易舉的解決:

 const makeRequest = async () => {
      try {
          // this parse may fail
          const data = JSON.parse(await getJSON())
          console.log(data)
      } 
      catch (err) {
          console.log(err)
      }
   }

條件判別Conditionals

想象一下這樣的業(yè)務(wù)需求:我們需要先拉取數(shù)據(jù),然后根據(jù)得到的數(shù)據(jù)判斷是否輸出此數(shù)據(jù)昧绣,或者根據(jù)數(shù)據(jù)內(nèi)容拉取更多的信息规肴。如下:

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
            }
        })
}

這樣的代碼會(huì)讓我們看的頭疼。這這么多層(6層)嵌套過程中夜畴,非常容易“丟失自我”拖刃。
使用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    
    }
}

中間值Intermediate values

一個(gè)經(jīng)常出現(xiàn)的場景是贪绘,我們先調(diào)起promise1兑牡,然后根據(jù)返回值,調(diào)用promise2税灌,之后再根據(jù)這兩個(gè)Promises得值均函,調(diào)取promise3。使用Promise菱涤,我們不難實(shí)現(xiàn):

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

如果你難以忍受這樣的代碼苞也,我們可以優(yōu)化我們的Promise,方案是使用Promise.all來避免很深的嵌套粘秆。
就像這樣:

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

Promise.all這個(gè)方法犧牲了語義性如迟,但是得到了更好的可讀性。
但是其實(shí)攻走,把value1 & value2一起放到一個(gè)數(shù)組中殷勘,是很“蛋疼”的,某種意義上也是多余的陋气。

同樣的場景劳吠,使用async/await會(huì)非常簡單:

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

錯(cuò)誤堆棧信息Error stacks

想象一下我們鏈?zhǔn)秸{(diào)用了很多promises,一級(jí)接一級(jí)巩趁。緊接著痒玩,這條promises鏈中某處出錯(cuò)淳附,如下:

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

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

此鏈條的錯(cuò)誤堆棧信息并沒用線索指示錯(cuò)誤到底出現(xiàn)在哪里。更糟糕的事蠢古,他還會(huì)誤導(dǎo)開發(fā)者:錯(cuò)誤信息中唯一出現(xiàn)的函數(shù)名稱其實(shí)根本就是無辜的奴曙。
我們再看一下async/await的展現(xià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ā)階段區(qū)別不是很大草讶。但是想象一下在服務(wù)器端洽糟,線上代碼的錯(cuò)誤日志情況下,將會(huì)變得非常有意義堕战。你一定會(huì)覺得上面這樣的錯(cuò)誤信息坤溃,比“錯(cuò)誤出自一個(gè)then的then的then。嘱丢。薪介。”有用的多越驻。

調(diào)試Debugging

最后一點(diǎn)汁政,但是也是很重要的一點(diǎn),使用async/await來debug會(huì)變得非常簡單缀旁。
在一個(gè)返回表達(dá)式的箭頭函數(shù)中记劈,我們不能設(shè)置斷點(diǎn),這就會(huì)造成下面的局面:

const makeRequest = () => {
    return callAPromise()
        .then(()=>callAPromise())
        .then(()=>callAPromise())
        .then(()=>callAPromise())
        .then(()=>callAPromise())
}

我們無法在每一行設(shè)置斷點(diǎn)并巍。但是使用async/await時(shí):

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

總結(jié)

Async/await是近些年來JavaScript最具革命性的新特性之一目木。他讓讀者意識(shí)到使用Promise存在的一些問題,并提供了自身來代替Promise的方案履澳。
當(dāng)然嘶窄,對這個(gè)新特性也有一定的擔(dān)心怀跛,體現(xiàn)在:
他使得異步代碼變的不再明顯距贷,我們好不容易已經(jīng)學(xué)會(huì)并習(xí)慣了使用回調(diào)函數(shù)或者.then來處理異步。新的特性當(dāng)然需要時(shí)間成本去學(xué)習(xí)和體會(huì)吻谋;
退回來說忠蝗,熟悉C#語言的程序員一定會(huì)懂得這些學(xué)習(xí)成本是完全值得的。

Happy Coding!

PS: 作者Github倉庫漓拾,歡迎通過代碼各種形式交流阁最。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市骇两,隨后出現(xiàn)的幾起案子速种,更是在濱河造成了極大的恐慌,老刑警劉巖低千,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件配阵,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)棋傍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門救拉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瘫拣,你說我怎么就攤上這事亿絮。” “怎么了麸拄?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵派昧,是天一觀的道長。 經(jīng)常有香客問我拢切,道長斗锭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任失球,我火速辦了婚禮岖是,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘实苞。我一直安慰自己豺撑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布黔牵。 她就那樣靜靜地躺著聪轿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猾浦。 梳的紋絲不亂的頭發(fā)上陆错,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音金赦,去河邊找鬼音瓷。 笑死,一個(gè)胖子當(dāng)著我的面吹牛夹抗,可吹牛的內(nèi)容都是我干的绳慎。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼漠烧,長吁一口氣:“原來是場噩夢啊……” “哼杏愤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起已脓,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對情侶失蹤珊楼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后度液,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厕宗,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡邓了,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了媳瞪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骗炉。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蛇受,靈堂內(nèi)的尸體忽然破棺而出句葵,到底是詐尸還是另有隱情,我是刑警寧澤兢仰,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布乍丈,位于F島的核電站,受9級(jí)特大地震影響把将,放射性物質(zhì)發(fā)生泄漏轻专。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一察蹲、第九天 我趴在偏房一處隱蔽的房頂上張望请垛。 院中可真熱鬧,春花似錦洽议、人聲如沸宗收。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽混稽。三九已至,卻和暖如春审胚,著一層夾襖步出監(jiān)牢的瞬間匈勋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國打工膳叨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洽洁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓懒鉴,卻偏偏與公主長得像诡挂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子临谱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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