一次搞懂 ES Promise

Promise

Promise 就像這個(gè)詞的表面意識(shí)一樣纹冤,表示一種承諾、許諾购公,會(huì)在后面給出一個(gè)結(jié)果萌京,成功 或者 失敗。現(xiàn)在已經(jīng)成為了主流的異步編程的操作方式宏浩,寫(xiě)進(jìn)了標(biāo)準(zhǔn)里面知残。

狀態(tài)

Promise 有且僅有三種狀態(tài):

  • 待定(pending): 初始狀態(tài),既沒(méi)有被兌現(xiàn)比庄,也沒(méi)有被拒絕求妹。
  • 已兌現(xiàn)(fulfilled): 意味著操作成功完成。
  • 已拒絕(rejected): 意味著操作失敗印蔗。

其實(shí)我覺(jué)得這個(gè)模型還是挺簡(jiǎn)單扒最,也很好理解丑勤。就好像华嘹,你跟你女朋友求婚,她跟你說(shuō)她要考慮一下法竞,明天才能給你答案耙厚,這就是承諾(promise)。同時(shí)岔霸,這也是一個(gè)等待的過(guò)程(pending)薛躬,然后你就等,等到明天你女朋友給你答復(fù)呆细,同意(fulfilled)或者拒絕(rejected)型宝,如果同意就準(zhǔn)備結(jié)婚了,如果不同意就等下次再求婚,哈哈哈趴酣。

那狀態(tài)要怎么改變呢梨树?

Promise 的構(gòu)造函數(shù)需要傳入一個(gè)函數(shù) executor ,這個(gè)函數(shù)需要兩個(gè)入?yún)⒎謩e是 resolverejected 兩個(gè)函數(shù)岖寞,這兩個(gè)方法主要是用來(lái)修改狀態(tài)的抡四。當(dāng)我們調(diào)用 resolve 函數(shù)的時(shí)候,Promise 的狀態(tài)就變成 fulfilled 仗谆。當(dāng)我們調(diào)用 reject 函數(shù)的時(shí)候指巡,Promise 的狀態(tài)就變成 reject 。舉個(gè)栗子:

new Promise((resolve, reject) => {
    console.log("開(kāi)始求婚隶垮。")
    console.log("藻雪。。岁疼。阔涉。。")
    console.log("考慮一下捷绒。")
    setTimeout(() => {
        if (isHandsome || isRich) {
            resolve('我同意瑰排!')
        } else {
            reject("拒絕:我們八字不合")
        }
    }, 2000)
})

這里還要注意一點(diǎn),如果一個(gè) promise 已經(jīng)被兌現(xiàn)(fulfilled)或被拒絕(rejected)暖侨,那么我們也可以說(shuō)它處于已敲定(settled)狀態(tài)椭住。

鏈?zhǔn)秸{(diào)用

這點(diǎn)跟 Rx 還是挺相似的,也是鏈?zhǔn)秸{(diào)用字逗,這樣的好處就是異步邏輯更加清晰京郑,連貫,符合直覺(jué)葫掉,避免了回調(diào)地獄些举。

我們可以用 Promise.then()Promise.catch()Promise.finally() 這些方法將進(jìn)一步的操作與一個(gè)變?yōu)橐亚枚顟B(tài)的 Promise 關(guān)聯(lián)起來(lái)俭厚。這些方法還會(huì)返回一個(gè)新生成的 Promise 對(duì)象户魏,這個(gè)對(duì)象可以被非強(qiáng)制性的用來(lái)做鏈?zhǔn)秸{(diào)用,就像這樣:

const myPromise =
  (new Promise(myExecutorFunc))
  .then(handleFulfilledA,handleRejectedA)
  .then(handleFulfilledB,handleRejectedB)
  .then(handleFulfilledC,handleRejectedC);

// 或者挪挤,這樣可能會(huì)更好...

const myPromise =
  (new Promise(myExecutorFunc))
  .then(handleFulfilledA)
  .then(handleFulfilledB)
  .then(handleFulfilledC)
  .catch(handleRejectedAny);

我們一般推薦使用第二種的寫(xiě)法叼丑,因?yàn)榈谝环N寫(xiě)法如果在中間出錯(cuò),在 .then 中處理完之后扛门,會(huì)繼續(xù)走下一個(gè) .thenonfulfilled 函數(shù)鸠信,不太符合直覺(jué)。當(dāng)然论寨,如果你有馬上處理異常的需求也可以這樣寫(xiě)星立。

當(dāng) .then() 中缺少能夠返回 Promise 對(duì)象的函數(shù)時(shí)爽茴,鏈?zhǔn)秸{(diào)用就直接繼續(xù)進(jìn)行下一環(huán)操作。因此绰垂,鏈?zhǔn)秸{(diào)用可以在最后一個(gè) .catch() 之前把所有的 handleRejection 都省略掉闹啦。類似地, .catch() 其實(shí)只是沒(méi)有給 handleFulfilled 預(yù)留參數(shù)位置的 .then() 而已辕坝。

還是一個(gè) Promise

這里要特別注意一個(gè)點(diǎn)窍奋,Promise.then()Promise.catch()酱畅、Promise.finally() 這些方法會(huì)返回一個(gè)新的 Promise琳袄,這一點(diǎn)在其他很多文章中會(huì)忽略這個(gè)點(diǎn)。 .then() 函數(shù)最后會(huì)返回一個(gè) Promise 纺酸,如果在 .thenonfulfilled (也就是入?yún)⒌牡谝粋€(gè)函數(shù))中返回一個(gè)值窖逗,或者對(duì)象,這個(gè) Promise 的狀態(tài)就會(huì)確定(settled)餐蔬,接著會(huì)觸發(fā)下個(gè) .then() 中的 onfulfilled 碎紊,并將這個(gè) 值 或者 對(duì)象作為入?yún)魅搿?/p>

誒,等一下樊诺,如果在 .then() 中直接返回一個(gè) Promise 呢仗考?

這個(gè)時(shí)候就會(huì)直接替換返回的 Promise 。就跟 RxJava 中的 flatMap 操作挺相似的词爬。MDN 中有說(shuō)明:

鏈?zhǔn)秸{(diào)用中的 promise 們就像俄羅斯套娃一樣秃嗜,是嵌套起來(lái)的,但又像是一個(gè)棧顿膨,每個(gè)都必須從頂端被彈出锅锨。鏈?zhǔn)秸{(diào)用中的第一個(gè) promise 是嵌套最深的一個(gè),也將是第一個(gè)被彈出的恋沃。

(promise D, (promise C, (promise B, (promise A) ) ) )

當(dāng)存在一個(gè) nextValue 是 promise 時(shí)必搞,就會(huì)出現(xiàn)一種動(dòng)態(tài)的替換效果。return 會(huì)導(dǎo)致一個(gè) promise 被彈出囊咏,但這個(gè) nextValue promise 則會(huì)被推入被彈出 promise 原來(lái)的位置恕洲。對(duì)于上面所示的嵌套場(chǎng)景,假設(shè)與 "promise B" 相關(guān)的 .then() 返回了一個(gè)值為 "promise X" 的 nextValue 匆笤。那么嵌套的結(jié)果看起來(lái)就會(huì)是這樣:

(promise D, (promise C, (promise X) ) )

觀察者模式研侣?

而且一個(gè) Promise 可能會(huì)參與不止一次的嵌套谱邪,感覺(jué)也像是觀察者的模式炮捧,并支持多個(gè)觀察者。對(duì)于下面的代碼惦银,promiseA 向"已敲定"("settled")狀態(tài)的過(guò)渡會(huì)導(dǎo)致兩個(gè)實(shí)例的 .then 都被調(diào)用咆课。

const promiseA = new Promise(myExecutorFunc);
const promiseB = promiseA.then(handleFulfilled1, handleRejected1);
const promiseC = promiseA.then(handleFulfilled2, handleRejected2); 

寫(xiě)點(diǎn)代碼

講了這么多末誓,寫(xiě)點(diǎn)代碼吧,就寫(xiě)一個(gè)书蚪,女朋友多次拒絕之后看到了你的誠(chéng)意喇澡,最后同意的故事。手動(dòng)狗頭殊校。

let count = 0

const propose = () => {
    return new Promise((resolve, reject) => {
        console.log("開(kāi)始求婚晴玖。")
        console.log("。为流。呕屎。。敬察。")
        console.log("考慮一下秀睛。")
        setTimeout(() => {
            if (count < 3) {
                reject("拒絕:我們八字不合")
                count++
            } else {
                resolve('我同意!')
            }
        }, 2000)
    })
}

const startPropose = () => {
    propose()
        .then((result) => {
            console.log(result)
            console.log("完結(jié)撒花")
            console.log("莲祸、·蹂安、·、锐帜、·田盈、·、·缴阎、")
        },)
        .catch((e) => {
            {
                console.error(e)
                console.log("被拒絕了缠黍,下次再求婚。")
                setTimeout(() => {
                    startPropose()
                }, 3000)
            }
        })
}

startPropose()

補(bǔ)充:

還有一些小細(xì)節(jié)药蜻。

  1. Promise 一旦創(chuàng)建瓷式,傳入的 executor 會(huì)被立刻執(zhí)行。

  2. 一個(gè)“已敲定”("settled")狀態(tài)的 Promise 也可以接受操作语泽,也就是說(shuō)贸典,后面可以接 then 或者 catch 函數(shù)。

  3. 所有 Promise 都是異步的踱卵,即便是“已敲定”(“settled”)了的 Promise 也是如此廊驼。一個(gè)已經(jīng)處于"已敲定"("settled")狀態(tài)的 promise 中的操作只有 promise 鏈?zhǔn)秸{(diào)用的棧被清空了和一個(gè)事件循環(huán)過(guò)去了之后才會(huì)被執(zhí)行。這種效果跟 setTimeout(action, 10) 特別相似惋砂。

    const promiseA = new Promise( (resolutionFunc,rejectionFunc) => {
        resolutionFunc(777);
    });
    // 這時(shí)妒挎,"promiseA" 已經(jīng)被敲定了。
    promiseA.then( (val) => console.log("asynchronous logging has val:",val) );
    console.log("immediate logging");
    
    // produces output in this order:
    // immediate logging
    // asynchronous logging has val: 777
    
  4. 其他一些靜態(tài)方法

    • Promise.all(iterable)

      這個(gè)方法返回一個(gè)新的promise對(duì)象西饵,該promise對(duì)象在iterable參數(shù)對(duì)象里所有的promise對(duì)象都成功的時(shí)候才會(huì)觸發(fā)成功酝掩,一旦有任何一個(gè)iterable里面的promise對(duì)象失敗則立即觸發(fā)該promise對(duì)象的失敗。這個(gè)新的promise對(duì)象在觸發(fā)成功狀態(tài)以后眷柔,會(huì)把一個(gè)包含iterable里所有promise返回值的數(shù)組作為成功回調(diào)的返回值期虾,順序跟iterable的順序保持一致原朝;如果這個(gè)新的promise對(duì)象觸發(fā)了失敗狀態(tài),它會(huì)把iterable里第一個(gè)觸發(fā)失敗的promise對(duì)象的錯(cuò)誤信息作為它的失敗錯(cuò)誤信息镶苞。Promise.all方法常被用于處理多個(gè)promise對(duì)象的狀態(tài)集合喳坠。(可以參考jQuery.when方法---譯者注)

    • Promise.allSettled(iterable)

      等到所有promises都已敲定(settled)(每個(gè)promise都已兌現(xiàn)(fulfilled)或已拒絕(rejected))。 返回一個(gè)promise茂蚓,該promise在所有promise完成后完成壕鹉。并帶有一個(gè)對(duì)象數(shù)組,每個(gè)對(duì)象對(duì)應(yīng)每個(gè)promise的結(jié)果聋涨。

    • Promise.any(iterable)

      接收一個(gè)Promise對(duì)象的集合御板,當(dāng)其中的一個(gè) promise 成功,就返回那個(gè)成功的promise的值牛郑。

    • Promise.race(iterable)

      當(dāng)iterable參數(shù)里的任意一個(gè)子promise被成功或失敗后怠肋,父promise馬上也會(huì)用子promise的成功返回值或失敗詳情作為參數(shù)調(diào)用父promise綁定的相應(yīng)句柄,并返回該promise對(duì)象淹朋。

    • Promise.reject(reason)

      返回一個(gè)狀態(tài)為失敗的Promise對(duì)象笙各,并將給定的失敗信息傳遞給對(duì)應(yīng)的處理方法

    • Promise.resolve(value)

      返回一個(gè)狀態(tài)由給定value決定的Promise對(duì)象。如果該值是thenable(即础芍,帶有then方法的對(duì)象)杈抢,返回的Promise對(duì)象的最終狀態(tài)由then方法執(zhí)行決定;否則的話(該value為空仑性,基本類型或者不帶then方法的對(duì)象),返回的Promise對(duì)象狀態(tài)為fulfilled惶楼,并且將該value傳遞給對(duì)應(yīng)的then方法。通常而言诊杆,如果您不知道一個(gè)值是否是Promise對(duì)象歼捐,使用Promise.resolve(value) 來(lái)返回一個(gè)Promise對(duì)象,這樣就能將該value以Promise對(duì)象形式使用。

參考:MDN(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末晨汹,一起剝皮案震驚了整個(gè)濱河市豹储,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌淘这,老刑警劉巖剥扣,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異铝穷,居然都是意外死亡钠怯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)曙聂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)晦炊,“玉大人,你說(shuō)我怎么就攤上這事」舸福” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵朦佩,是天一觀的道長(zhǎng)并思。 經(jīng)常有香客問(wèn)我,道長(zhǎng)语稠,這世上最難降的妖魔是什么宋彼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮仙畦,結(jié)果婚禮上输涕,老公的妹妹穿的比我還像新娘。我一直安慰自己慨畸,他們只是感情好莱坎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著寸士,像睡著了一般檐什。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弱卡,一...
    開(kāi)封第一講書(shū)人閱讀 51,775評(píng)論 1 307
  • 那天乃正,我揣著相機(jī)與錄音,去河邊找鬼婶博。 笑死瓮具,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的凡人。 我是一名探鬼主播名党,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挠轴!你這毒婦竟也來(lái)了兑巾?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤忠荞,失蹤者是張志新(化名)和其女友劉穎蒋歌,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體委煤,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡堂油,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碧绞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片府框。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖讥邻,靈堂內(nèi)的尸體忽然破棺而出迫靖,到底是詐尸還是另有隱情院峡,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布系宜,位于F島的核電站照激,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏盹牧。R本人自食惡果不足惜俩垃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望汰寓。 院中可真熱鬧口柳,春花似錦、人聲如沸有滑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)毛好。三九已至辣卒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間睛榄,已是汗流浹背荣茫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留场靴,地道東北人啡莉。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像旨剥,于是被迫代替她去往敵國(guó)和親咧欣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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