Promise和Async/await

閱讀本篇文章之前一定要明白異步解決方案和 http 的關(guān)系。異步是異步囚聚,http 是http跨晴。http 請(qǐng)求是一個(gè)異步的過(guò)程,而異步并不一定就和 http 請(qǐng)求聯(lián)系在一起根吁。所以不要提起異步就默認(rèn)為就是 ajax請(qǐng)求數(shù)據(jù)员淫。

Promise

Promise 對(duì)象用于表示一個(gè)異步操作的最終狀態(tài)(玩成或失敗)击敌,以及其返回的值介返。

看下控制臺(tái)輸出的 Promise 對(duì)象信息:


promise.png

Promise 的構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù)。傳入的函數(shù)參數(shù)可以有兩個(gè): resolve 和 reject沃斤。resolve 是將 Promise 從 pending 狀態(tài)置為 fulfilled 狀態(tài)圣蝎。 reject 是將 Promise 狀態(tài)從 pending 置為 rejected 狀態(tài)。我們可以簡(jiǎn)單的理解成:resolve 表示異步操作成功后的回調(diào)函數(shù)衡瓶, reject 表示異步操作失敗后的回調(diào)函數(shù)徘公。

Promise 最直接的好處就是鏈?zhǔn)秸{(diào)用。

沒(méi)有使用 Promise 來(lái)實(shí)現(xiàn)異步操作:

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

按上面的寫法哮针,做很多的多層回調(diào)會(huì)讓我們陷入經(jīng)典的回調(diào)地獄关面。

而使用 Promise 來(lái)實(shí)現(xiàn)異步多層回調(diào):

doSomething()
.then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

對(duì)比于我們可能會(huì)陷入的回調(diào)地獄坦袍,Promise 簡(jiǎn)化了層層回調(diào)的寫法。而且缭裆,用維護(hù)狀態(tài)键闺、傳遞狀態(tài)的方式來(lái)使得回調(diào)函數(shù)能夠及時(shí)調(diào)用,這比傳遞 callback 函數(shù)要簡(jiǎn)單澈驼、靈活的多辛燥。

在 then 方法中,我們除了可以 return 一個(gè) Promise 對(duì)象缝其,還可以直接 return 數(shù)據(jù)挎塌,可以在之后的 then 中接收到 return 數(shù)據(jù)了:


function runAsync1(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務(wù)1執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)1');
        }, 1000);
    });
    return p;            
}
function runAsync2(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            console.log('異步任務(wù)2執(zhí)行完成');
            resolve('隨便什么數(shù)據(jù)2');
        }, 2000);
    });
    return p;            
}

runAsync1()
.then(function(data){
    console.log(data + '@@');
    return runAsync2();
})
.then(function(data){
    console.log(data + '##');
    return '直接返回?cái)?shù)據(jù)';  //這里直接返回?cái)?shù)據(jù)
})
.then(function(data){
    console.log(data + '--');
});

上面的例子只是講了 Promise 的用法,其中涉及到的只有成功時(shí)回調(diào)的 resolve,那失敗狀態(tài)又是如何使用的呢内边?

function getNumber(){
    var p = new Promise(function(resolve, reject){
        //做一些異步操作
        setTimeout(function(){
            var num = Math.ceil(Math.random()*10); //生成1-10的隨機(jī)數(shù)
            if(num<=5){
                resolve(num);
            }
            else{
                reject('數(shù)字太大了');
            }
        }, 2000);
    });
    return p;            
}
 
getNumber()
.then(
    function(data){
        console.log('resolved');
        console.log(data);
    }, 
    function(reason, data){
        console.log('rejected');
        console.log(reason);
    }
);

我們?cè)?getNumber 函數(shù)中調(diào)用 Promise 中的 reject 來(lái)將某種失敗的狀態(tài)傳遞出來(lái)榴都,然后在 then 中傳遞了兩個(gè)參數(shù)。 then 方法可以接受兩個(gè)參數(shù)漠其,第一個(gè)對(duì)應(yīng) resolve 的回調(diào)嘴高,第二個(gè)對(duì)應(yīng) reject 的回調(diào)。所以在這兩個(gè)回調(diào)函數(shù)中和屎,我們可以獲取對(duì)應(yīng)的成功或失敗返回的數(shù)據(jù)拴驮。

我們常用的 Promise 對(duì)象除了 then 方法外,還有一個(gè) catch 方法柴信。該方法是用來(lái)干啥的呢套啤?then 里的參數(shù)是可選的, catch(failureCallback)then(null, failureCallback) 的縮略形式随常。catch 的作用其實(shí)和 then 的第二個(gè)參數(shù)一樣潜沦,用來(lái)指定 reject 的回調(diào):

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

catch 除了可以用來(lái)表示 reject 的回調(diào)外,它還有另外一個(gè)作用:在執(zhí)行 resolve 的回調(diào)(也就是上面代碼 then 中的第一個(gè)參數(shù))時(shí)绪氛,如果拋出異常了(代碼出錯(cuò)了)唆鸡,那么并不會(huì)報(bào)錯(cuò)卡死 Js, 而是會(huì)進(jìn)到這個(gè) catch 方法中。

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
    console.log(somedata); //此處的somedata未定義
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

Promise.all()

Promise 的 all 方法提供了并行執(zhí)行異步操作的能力枣察,并且在所有異步操作執(zhí)行完后才執(zhí)行回調(diào)喇闸。

Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

用 Promise.all 來(lái)執(zhí)行,all 接收一個(gè)數(shù)組參數(shù)询件,里面的值最后都會(huì)返回 Promise 對(duì)象。這樣唆樊,三個(gè)異步操作并行執(zhí)行宛琅,等到它們都執(zhí)行完后才會(huì)進(jìn)到 then 里面。

Promise.race()

race 意為 “競(jìng)爭(zhēng)”逗旁。 all 方法的效果實(shí)際上是誰(shuí)跑的慢嘿辟,以誰(shuí)為準(zhǔn)執(zhí)行回調(diào)舆瘪,進(jìn)而達(dá)到一個(gè)并行的效果。race 的效果則和 all 的效果相反红伦,誰(shuí)跑的最快就先回調(diào)執(zhí)行誰(shuí)英古。

Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});

用 Promise.race 來(lái)執(zhí)行,race 接收一個(gè)數(shù)組參數(shù)昙读,數(shù)組里面的值都是可以最終執(zhí)行返回 Promise 對(duì)象召调。調(diào)用執(zhí)行的時(shí)候都是并行執(zhí)行的,一旦數(shù)組參數(shù)中的某個(gè)對(duì)象執(zhí)行完并返回 Promise 對(duì)象就會(huì)立即進(jìn)入 then 方法繼續(xù)下一步蛮浑。而后面數(shù)組其他的對(duì)象執(zhí)行完依次重復(fù)該過(guò)程唠叛,體現(xiàn)了一個(gè)競(jìng)速的效果。

參考理解

Async/await

async/await是 es7 提出的異步特性

async字面意思是“異步”沮稚,用于聲明一個(gè)函數(shù)是異步的艺沼。await的字面意思是“等待”,用來(lái)等待異步函數(shù)完成蕴掏。

通常來(lái)說(shuō)async/await都是跟隨 Promise一起使用的障般。因?yàn)?code>async返回的是一個(gè) Promise對(duì)象。

/**
 * 成功執(zhí)行
 */
async function funcSuccess() {
    const count = 10;
    return count;
}

funcSuccess().then(
    (res) => {
        console.log(res);
    }
);

/**
 * 失敗執(zhí)行
 */
async function funcFail() {
    const count = 10 + i;
    console.log('測(cè)試是否繼續(xù)執(zhí)行');
    return count;
}

funcFail()
.then(
    (res) => {
        console.log('執(zhí)行成功', res);
    }
)
.catch(
    (error) => {
        console.log('執(zhí)行失敗', error);
    }
);

如果 async 函數(shù)執(zhí)行順利并結(jié)束盛杰,返回的 Promise 對(duì)象的狀態(tài)會(huì)從等待狀態(tài)轉(zhuǎn)變成功狀態(tài)挽荡,并輸出 return 命令返回的結(jié)果(沒(méi)有則為 undefined)。如果 async 函數(shù)執(zhí)行途中失敗饶唤,JS 會(huì)認(rèn)為 async 函數(shù)已經(jīng)完成執(zhí)行徐伐,返回的 Promise 對(duì)象的狀態(tài)會(huì)從等待轉(zhuǎn)變成失敗,并輸出錯(cuò)誤信息募狂。

await只能用在async函數(shù)里面办素,存在于 async 內(nèi)部的普通函數(shù)也不行。

/**
 * await 只能用在 async 函數(shù)內(nèi)部祸穷,存在于 async 函數(shù)內(nèi)部的普通函數(shù)也不行
 * 下面這段代碼會(huì)直接報(bào)錯(cuò)
 */
async function testA() {
  function abc () {
      const ab = await new Promise(resolve => {
        setTimeout(() => {
          resolve(10);
        }, 2000);
      }); 
  }
}

引擎會(huì)統(tǒng)一將 await 后面的跟隨值視為一個(gè) Promise性穿, 對(duì)于不是 Promise 對(duì)象的值會(huì)調(diào)用 Promise.resolve() 進(jìn)行轉(zhuǎn)化。即便此值為一個(gè) Error 實(shí)例雷滚,經(jīng)過(guò)轉(zhuǎn)化后需曾,引擎依然視其為一個(gè)成功的 Promise, 其數(shù)據(jù)為 Error 的實(shí)例祈远。

當(dāng)函數(shù)執(zhí)行到 await 命令時(shí)呆万,會(huì)暫停執(zhí)行并等待其后的 Promise 結(jié)束。如果該 Promise 對(duì)象最終成功车份,則會(huì)返回成功的返回值谋减,相當(dāng)于將 await xxx 替換成 返回值。如果該 Promise 對(duì)象最終失敗扫沼,且錯(cuò)誤沒(méi)有被捕獲出爹,引擎會(huì)直接停止執(zhí)行 async 函數(shù)并將其返回對(duì)象的狀態(tài)更改為失敗庄吼,輸出錯(cuò)誤信息。

async 函數(shù)中的 return x 表達(dá)式严就,相當(dāng)于 return await x 的簡(jiǎn)寫总寻。

/**
 * await 后面執(zhí)行成功
 */
async function awaitFuncSuccess() {
    const n1 = await 10;
    const n2 = await new Promise<number>((resolve) => {
        setTimeout(() => {
            resolve(20);
        }, 2000);
    });
    console.log('n1', n1, 'n2', n2);
    return n1 * n2;
}

awaitFuncSuccess()
.then(
    (res) => {
        console.log('執(zhí)行成功', res); // 約兩秒后 輸出 200
    }
)
.catch(
    (error) => {
        console.log('error', error);
    }
);

/**
 * await 后面執(zhí)行失敗
 */
async function awaitFuncFail() {
    const n1 = await 10;
    console.log('n1', n1);
    const n2 = await new Promise<number>((resolve, reject) => {
        setTimeout(() => {
            reject(20);
        }, 2000);
    });
    console.log('n1', n1, 'n2', n2);
    return n1 * n2;
}

awaitFuncFail()
.then(
    (res) => {
        console.log('執(zhí)行成功', res);
    }
)
.catch(
    (error) => {
        console.log('error', error); // 約兩秒后 輸出 20
    }
);

順序發(fā)生

/**
 * 順序執(zhí)行
 */
async function queueFunc_A() {
    const n1 = await createPromise();
    console.log('n1', n1);
    const n2 = await createPromise();
    console.log('n2', n2);
    const n3 = await createPromise();
    console.log('n3', n3);
}

function createPromise() {
    return new Promise((resolve) => {
        setTimeout(() => {
            setTimeout(() => {
                resolve(10);
            });
        }, 2000);
    });
}

async function queueFunc_B() {
    for (let i = 0; i < 3; i ++) {
        let n = await createPromise();
        console.log('N' + (i + 1), n);
    }
}

// 運(yùn)行下面兩個(gè)函數(shù),都是間隔兩秒依次輸出
queueFunc_A();
queueFunc_B();

并發(fā)執(zhí)行

仔細(xì)查看下面的代碼是如何使用數(shù)組進(jìn)行并行執(zhí)行的

/**
 * 并行執(zhí)行
 */
async function parallelFunc_A() {
    const res = await Promise.all([createPromise(), createPromise(), createPromise()]);
    console.log('Data', res);
}

async function parallelFunc_B() {
    let res = [];
    const reqs = [createPromise(), createPromise(), createPromise()];
    for (let i = 0; i < reqs.length; i++) {
        res[i] = await reqs[i];
    }
    console.log('Data', res);
}

async function parallelFunc_C() {
    let res = [];
    let reqs = [1,2,3].map(
        async (item) => {
            let n = await createPromise();
            return n + 1;
        }
    );
    for (let i = 0; i < reqs.length; i++) {
        res[i] = await reqs[i]
    }
    console.log('Data', res);
}

錯(cuò)誤處理

一旦 await 后面的 Promise 轉(zhuǎn)變成 rejected, 整個(gè) async 函數(shù)便會(huì)終止梢为。然而很多時(shí)候我們不希望因?yàn)槟硞€(gè)異步操作的失敗渐行,就終止整個(gè)函數(shù),因此需要進(jìn)行合理錯(cuò)誤處理抖誉。注意殊轴,這里所說(shuō)的錯(cuò)誤不包括引擎解析或執(zhí)行的錯(cuò)誤,僅僅是狀態(tài)變?yōu)?rejected 的 Promise 對(duì)象

處理錯(cuò)誤的方式有兩種: 一種是對(duì) Promise 對(duì)象進(jìn)行包裝袒炉,使其始終返回一個(gè)成功的 Promise 旁理。 二是使用 try/catch 捕獲錯(cuò)誤。

/**
 * 錯(cuò)誤處理
 */
async function errorFunc_A() {
    let n;
    n = await createPromiseByParam(true);
    return n;
}

async function errorFunc_B() {
    let n;
    try {
        n = await createPromiseByParam(false);
    } catch (e) {
        n = e;
    }
    return n;
}

function createPromiseByParam(param: boolean) {
    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('出錯(cuò)啦我磁!')
        }, 1000);
    });
    return param ? p.catch((error) => { return 'catch error' + error}) : p;
}

// async 返回的是一個(gè) Promise 對(duì)象孽文,執(zhí)行下面的函數(shù)獲取結(jié)果
errorFunc_A().then(console.log);
errorFunc_B().then(console.log);

閱讀參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市夺艰,隨后出現(xiàn)的幾起案子芋哭,更是在濱河造成了極大的恐慌,老刑警劉巖郁副,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件减牺,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡存谎,警方通過(guò)查閱死者的電腦和手機(jī)拔疚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)既荚,“玉大人稚失,你說(shuō)我怎么就攤上這事∏∑福” “怎么了句各?”我有些...
    開(kāi)封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)晴叨。 經(jīng)常有香客問(wèn)我凿宾,道長(zhǎng),這世上最難降的妖魔是什么兼蕊? 我笑而不...
    開(kāi)封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任菌湃,我火速辦了婚禮,結(jié)果婚禮上遍略,老公的妹妹穿的比我還像新娘惧所。我一直安慰自己,他們只是感情好绪杏,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布下愈。 她就那樣靜靜地躺著,像睡著了一般蕾久。 火紅的嫁衣襯著肌膚如雪势似。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天僧著,我揣著相機(jī)與錄音履因,去河邊找鬼。 笑死盹愚,一個(gè)胖子當(dāng)著我的面吹牛栅迄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播皆怕,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼毅舆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了愈腾?” 一聲冷哼從身側(cè)響起憋活,我...
    開(kāi)封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虱黄,沒(méi)想到半個(gè)月后悦即,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡橱乱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年辜梳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仅醇。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冗美,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出析二,到底是詐尸還是另有隱情粉洼,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布叶摄,位于F島的核電站属韧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蛤吓。R本人自食惡果不足惜宵喂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望会傲。 院中可真熱鬧锅棕,春花似錦拙泽、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至德绿,卻和暖如春荷荤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背移稳。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工蕴纳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人个粱。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓古毛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親几蜻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子喇潘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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