2020-09-03

原文章鏈接:https://segmentfault.com/a/1190000007535316
作者:邊城

1. async 和 await 在干什么

任意一個(gè)名稱都是有意義的,先從字面意思來理解橄镜。async 是“異步”的簡寫粘我,而 await 可以認(rèn)為是 async wait 的簡寫怎燥。所以應(yīng)該很好理解 async 用于申明一個(gè) function 是異步的操灿,而 await 用于等待一個(gè)異步方法執(zhí)行完成俯画。

另外還有一個(gè)很有意思的語法規(guī)定排抬,await 只能出現(xiàn)在 async 函數(shù)中栈拖。然后細(xì)心的朋友會產(chǎn)生一個(gè)疑問连舍,如果 await 只能出現(xiàn)在 async 函數(shù)中,那這個(gè) async 函數(shù)應(yīng)該怎么調(diào)用?

如果需要通過 await 來調(diào)用一個(gè) async 函數(shù)索赏,那這個(gè)調(diào)用的外面必須得再包一個(gè) async 函數(shù)盼玄,然后……進(jìn)入死循環(huán),永無出頭之日……

如果 async 函數(shù)不需要 await 來調(diào)用潜腻,那 async 到底起個(gè)啥作用埃儿?

1.1. async 起什么作用

這個(gè)問題的關(guān)鍵在于,async 函數(shù)是怎么處理它的返回值的融涣!

我們當(dāng)然希望它能直接通過 return 語句返回我們想要的值童番,但是如果真是這樣,似乎就沒 await 什么事了威鹿。所以剃斧,寫段代碼來試試,看它到底會返回什么:

async function testAsync() {
    return "hello async";
}

const result = testAsync();
console.log(result);

看到輸出就恍然大悟了——輸出的是一個(gè) Promise 對象忽你。

c:\var\test> node --harmony_async_await .
Promise { 'hello async' }

所以幼东,async 函數(shù)返回的是一個(gè) Promise 對象。從文檔中也可以得到這個(gè)信息科雳。async 函數(shù)(包含函數(shù)語句根蟹、函數(shù)表達(dá)式、Lambda表達(dá)式)會返回一個(gè) Promise 對象糟秘,如果在函數(shù)中 return 一個(gè)直接量简逮,async 會把這個(gè)直接量通過 Promise.resolve() 封裝成 Promise 對象。

補(bǔ)充知識點(diǎn) [2020-06-04]

Promise.resolve(x) 可以看作是 new Promise(resolve => resolve(x)) 的簡寫蚌堵,可以用于快速封裝字面量對象或其他對象买决,將其封裝成 Promise 實(shí)例沛婴。

async 函數(shù)返回的是一個(gè) Promise 對象吼畏,所以在最外層不能用 await 獲取其返回值的情況下,我們當(dāng)然應(yīng)該用原來的方式:then() 鏈來處理這個(gè) Promise 對象嘁灯,就像這樣

testAsync().then(v => {
    console.log(v);    // 輸出 hello async
});

現(xiàn)在回過頭來想下泻蚊,如果 async 函數(shù)沒有返回值,又該如何丑婿?很容易想到性雄,它會返回 Promise.resolve(undefined)

聯(lián)想一下 Promise 的特點(diǎn)——無等待羹奉,所以在沒有 await 的情況下執(zhí)行 async 函數(shù)秒旋,它會立即執(zhí)行,返回一個(gè) Promise 對象诀拭,并且迁筛,絕不會阻塞后面的語句。這和普通返回 Promise 對象的函數(shù)并無二致耕挨。

那么下一個(gè)關(guān)鍵點(diǎn)就在于 await 關(guān)鍵字了细卧。

1.2. await 到底在等啥

一般來說尉桩,都認(rèn)為 await 是在等待一個(gè) async 函數(shù)完成。不過按語法說明贪庙,await 等待的是一個(gè)表達(dá)式蜘犁,這個(gè)表達(dá)式的計(jì)算結(jié)果是 Promise 對象或者其它值(換句話說,就是沒有特殊限定)止邮。

因?yàn)?async 函數(shù)返回一個(gè) Promise 對象这橙,所以 await 可以用于等待一個(gè) async 函數(shù)的返回值——這也可以說是 await 在等 async 函數(shù),但要清楚导披,它等的實(shí)際是一個(gè)返回值析恋。注意到 await 不僅僅用于等 Promise 對象,它可以等任意表達(dá)式的結(jié)果盛卡,所以助隧,await 后面實(shí)際是可以接普通函數(shù)調(diào)用或者直接量的。所以下面這個(gè)示例完全可以正確運(yùn)行

function getSomething() {
    return "something";
}

async function testAsync() {
    return Promise.resolve("hello async");
}

async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
}

test();

1.3. await 等到了要等的滑沧,然后呢

await 等到了它要等的東西并村,一個(gè) Promise 對象,或者其它值滓技,然后呢哩牍?我不得不先說,await 是個(gè)運(yùn)算符令漂,用于組成表達(dá)式膝昆,await 表達(dá)式的運(yùn)算結(jié)果取決于它等的東西。

如果它等到的不是一個(gè) Promise 對象叠必,那 await 表達(dá)式的運(yùn)算結(jié)果就是它等到的東西荚孵。

如果它等到的是一個(gè) Promise 對象,await 就忙起來了纬朝,它會阻塞后面的代碼收叶,等著 Promise 對象 resolve,然后得到 resolve 的值共苛,作為 await 表達(dá)式的運(yùn)算結(jié)果判没。

看到上面的阻塞一詞,心慌了吧……放心隅茎,這就是 await 必須用在 async 函數(shù)中的原因澄峰。async 函數(shù)調(diào)用不會造成阻塞,它內(nèi)部所有的阻塞都被封裝在一個(gè) Promise 對象中異步執(zhí)行辟犀。

2. async/await 幫我們干了啥

2.1. 作個(gè)簡單的比較

上面已經(jīng)說明了 async 會將其后的函數(shù)(函數(shù)表達(dá)式或 Lambda)的返回值封裝成一個(gè) Promise 對象俏竞,而 await 會等待這個(gè) Promise 完成,并將其 resolve 的結(jié)果返回出來。

現(xiàn)在舉例胞此,用 setTimeout 模擬耗時(shí)的異步操作臣咖,先來看看不用 async/await 會怎么寫

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

takeLongTime().then(v => {
    console.log("got", v);
});

如果改用 async/await 呢,會是這樣

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

async function test() {
    const v = await takeLongTime();
    console.log(v);
}

test();

眼尖的同學(xué)已經(jīng)發(fā)現(xiàn) takeLongTime() 沒有申明為 async漱牵。實(shí)際上夺蛇,takeLongTime() 本身就是返回的 Promise 對象,加不加 async 結(jié)果都一樣酣胀,如果沒明白刁赦,請回過頭再去看看上面的“async 起什么作用”。

又一個(gè)疑問產(chǎn)生了闻镶,這兩段代碼甚脉,兩種方式對異步調(diào)用的處理(實(shí)際就是對 Promise 對象的處理)差別并不明顯,甚至使用 async/await 還需要多寫一些代碼铆农,那它的優(yōu)勢到底在哪牺氨?

2.2. async/await 的優(yōu)勢在于處理 then 鏈

單一的 Promise 鏈并不能發(fā)現(xiàn) async/await 的優(yōu)勢,但是墩剖,如果需要處理由多個(gè) Promise 組成的 then 鏈的時(shí)候猴凹,優(yōu)勢就能體現(xiàn)出來了(很有意思,Promise 通過 then 鏈來解決多層回調(diào)的問題岭皂,現(xiàn)在又用 async/await 來進(jìn)一步優(yōu)化它)郊霎。

假設(shè)一個(gè)業(yè)務(wù),分多個(gè)步驟完成爷绘,每個(gè)步驟都是異步的书劝,而且依賴于上一個(gè)步驟的結(jié)果。我們?nèi)匀挥?setTimeout 來模擬異步操作:

/**
 * 傳入?yún)?shù) n土至,表示這個(gè)函數(shù)執(zhí)行的時(shí)間(毫秒)
 * 執(zhí)行的結(jié)果是 n + 200购对,這個(gè)值將用于下一步驟
 */
function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}

function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}

現(xiàn)在用 Promise 方式來實(shí)現(xiàn)這三個(gè)步驟的處理

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();

// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms

輸出結(jié)果 resultstep3() 的參數(shù) 700 + 200 = 900doIt() 順序執(zhí)行了三個(gè)步驟毙籽,一共用了 300 + 500 + 700 = 1500 毫秒洞斯,和 console.time()/console.timeEnd() 計(jì)算的結(jié)果一致。

如果用 async/await 來實(shí)現(xiàn)呢坑赡,會是這樣

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();

結(jié)果和之前的 Promise 實(shí)現(xiàn)是一樣的,但是這個(gè)代碼看起來是不是清晰得多么抗,幾乎跟同步代碼一樣

2.3. 還有更酷的

現(xiàn)在把業(yè)務(wù)要求改一下毅否,仍然是三個(gè)步驟,但每一個(gè)步驟都需要之前每個(gè)步驟的結(jié)果蝇刀。

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(m, n) {
    console.log(`step2 with ${m} and ${n}`);
    return takeLongTime(m + n);
}

function step3(k, m, n) {
    console.log(`step3 with ${k}, ${m} and ${n}`);
    return takeLongTime(k + m + n);
}

這回先用 async/await 來寫:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time1, time2);
    const result = await step3(time1, time2, time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();

// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms

除了覺得執(zhí)行時(shí)間變長了之外螟加,似乎和之前的示例沒啥區(qū)別啊!別急捆探,認(rèn)真想想如果把它寫成 Promise 方式實(shí)現(xiàn)會是什么樣子然爆?

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => {
            return step2(time1, time2)
                .then(time3 => [time1, time2, time3]);
        })
        .then(times => {
            const [time1, time2, time3] = times;
            return step3(time1, time2, time3);
        })
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();

有沒有感覺有點(diǎn)復(fù)雜的樣子?那一堆參數(shù)處理黍图,就是 Promise 方案的死穴—— 參數(shù)傳遞太麻煩了曾雕,看著就暈!

3. 洗洗睡吧

就目前來說助被,已經(jīng)理解 async/await 了吧剖张?但其實(shí)還有一些事情沒提及——Promise 有可能 reject 啊,怎么處理呢揩环?如果需要并行處理3個(gè)步驟搔弄,再等待所有結(jié)果,又該怎么處理呢丰滑?

阮一峰老師已經(jīng)說過了顾犹,我就懶得說了。

4. 推薦相關(guān)文章

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市貌亭,隨后出現(xiàn)的幾起案子柬唯,更是在濱河造成了極大的恐慌,老刑警劉巖圃庭,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锄奢,死亡現(xiàn)場離奇詭異,居然都是意外死亡剧腻,警方通過查閱死者的電腦和手機(jī)拘央,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來书在,“玉大人灰伟,你說我怎么就攤上這事∪逖” “怎么了栏账?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長栈源。 經(jīng)常有香客問我挡爵,道長,這世上最難降的妖魔是什么甚垦? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任茶鹃,我火速辦了婚禮涣雕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闭翩。我一直安慰自己挣郭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布疗韵。 她就那樣靜靜地躺著兑障,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伶棒。 梳的紋絲不亂的頭發(fā)上旺垒,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機(jī)與錄音肤无,去河邊找鬼先蒋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宛渐,可吹牛的內(nèi)容都是我干的竞漾。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼窥翩,長吁一口氣:“原來是場噩夢啊……” “哼业岁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起寇蚊,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤笔时,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后仗岸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體允耿,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年扒怖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了较锡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盗痒,死狀恐怖蚂蕴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情俯邓,我是刑警寧澤骡楼,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站看成,受9級特大地震影響君编,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜川慌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一吃嘿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梦重,春花似錦兑燥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蚓胸,卻和暖如春挣饥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沛膳。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工扔枫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锹安。 一個(gè)月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓短荐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親叹哭。 傳聞我的和親對象是個(gè)殘疾皇子忍宋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361