ES6的Promise解決的哪些問題,以及使用方法和原理

事件和回調(diào)函數(shù)的缺陷

我們習(xí)慣于使用傳統(tǒng)的回調(diào)或者事件處理來解決異步問題

事件: 某個對象的屬性是一個函數(shù), 當(dāng)發(fā)生一件事時, 運行該函數(shù)

dom.onclick = function() {
    // ...執(zhí)行的代碼
}

回調(diào):運行某個函數(shù)以實現(xiàn)某個功能的時候, 傳入一個函數(shù)作為參數(shù), 當(dāng)某個特定的條件下的時候, 就會觸發(fā)該函數(shù)

dom.addEventListner('click', callback);

本質(zhì)上,事件和回調(diào)沒有本質(zhì)上的區(qū)別, 只是把函數(shù)放置的位置不同而已, 一直以來這個模式都運作良好,直到前端工作越來越復(fù)雜,目前該模式主要面臨以下兩個問題

  1. 回調(diào)地獄: 某個異步操作需要等待之前的異步操作完成, 無論是回調(diào)還是事件都會陷入不斷的嵌套,
    ajax({
        url, method, data, 
        success: res => {           // 當(dāng)某個請求數(shù)據(jù)的接口需要用到上一次請求回來的數(shù)據(jù)
            if(res.data) {          // 的時候, 我們就會生成回調(diào)地獄, 代碼非常的難看
                ajax({
                    url, method, res.data.data,
                    success: res => {
                        // ajax....
                    }
                })
            }
        }
    })
  1. 異步之間的聯(lián)系: 某個異步操作需要等待多個異步操作的結(jié)果, 對這種聯(lián)系的處理, 會讓代碼復(fù)雜度增加
// 小明像10個女神表白, 每個女神會思考部分時間以后再進(jìn)行回復(fù), 當(dāng)所有女神回復(fù)完畢以后, 小明統(tǒng)計日志
for(let i = 0, len = girArr.length; i < len; i++) {
    getResponse() // ..表白函數(shù)執(zhí)行, 每個表白函數(shù)中有一個setTimeout的異步來決定女神的思考時間,思考完成以后會推入result數(shù)組
}
if(reslut.length === 10) {
    // ...開始記錄日志  這樣會顯得相當(dāng)麻煩, 且不符合設(shè)計模式的單一原則
}

Promise

為了解決上述的問題, ES6引入了Promise, 這一塊概念文字較多(建議多讀幾遍至少對es6處理異步的通用模型有一定的基礎(chǔ)認(rèn)識,如果時間緊迫不想閱讀可以直接劃到下面看promise的實例講解)

我們先來看看ES6對異步處理的通用模型

ES6官方參考了大量的異步場景, 總結(jié)出了一套異步的通用模型, 該模型幾乎可以覆蓋所有的異步場景, 甚至是某些同步場景

值得注意的是, 為了兼容舊系統(tǒng), ES6并不打算拋掉過去的做法, 只是基于該模型推出了一個全新的api,使用該api會讓異步處理更加的簡潔和優(yōu)雅

理解該API, 最重要的是理解他的異步模型,

  1. ES6將某一件可能發(fā)生異步操作的事情, 分成兩個階段: unsettledsettled
image
  • unsettled: 未決階段, 表示事情還在進(jìn)行前期的處理, 并沒有發(fā)生通向結(jié)果的那件事(比如監(jiān)聽一個dom的點擊事件, 這時候用戶還未點擊)
  • settled: 已決階段, 事情已經(jīng)有了一個結(jié)果, 不管這個結(jié)果是好是壞, 整件事情無法逆轉(zhuǎn)(比如用戶觸發(fā)點擊事件已經(jīng)產(chǎn)生了結(jié)果)

事件總是從未決階段逐步發(fā)展到已決階段的, 未決階段擁有控制何時通向已決階段的能力

  1. 同時ES6將事情劃分為三種狀態(tài): padding, resolved, rejected
  • padding: 掛起, 處于未決階段, 最終結(jié)果還沒有出來
  • resolved: 已處理, 已決階段的一種狀態(tài), 表示整件事情已經(jīng)產(chǎn)生結(jié)果, 并且是一個可以按照正常邏輯進(jìn)行下去的結(jié)果。
  • rejected: 已拒絕, 已決階段的一種狀態(tài), 表示整件事情已經(jīng)產(chǎn)生結(jié)果, 并且是一個無法按照正常邏輯走下去的結(jié)果, 通常用于表示有錯誤

因為未決階段有能力決定事情的走向, 所以未決階段可以決定事情的最終走向

  • 我們將把事情變?yōu)閞esolved狀態(tài)的過程叫做resolve, 推向該狀態(tài)時, 可以傳遞一些數(shù)據(jù)
  • 我們把事情變?yōu)閞ejected狀態(tài)的過程叫做reject, 推向該狀態(tài)時, 通常傳遞一些錯誤

始終記住, 無論是階段還是狀態(tài)都是不可逆轉(zhuǎn)的

  1. 當(dāng)事情發(fā)展到已決階段以后, 通常需要進(jìn)行后續(xù)處理, 不同的已決狀態(tài), 決定了不同的后續(xù)處理
  • resolved: 后續(xù)處理為thenable
  • rejected: 后續(xù)處理為catchable

后續(xù)處理可能會有很多個, 因此會形成作業(yè)隊列, 這些后續(xù)處理會按照順序, 當(dāng)狀態(tài)到達(dá)后依次執(zhí)行

ES6把上述一系列的概念和操作, 取了一個很優(yōu)雅的名字叫做Promise(承諾)

Pormise的基本使用

Pormise是一個構(gòu)造類, 他接受一個函數(shù)作為參數(shù), 如下

const promise = new Promise((resolve, rejected) => {
    /*
    未決處理的階段
    ajax請求等異步操作可以放在這兒
    通過調(diào)用resolve函數(shù)將Promise推向已決的resolved狀態(tài)
    通過調(diào)用rejected將Promise推向已決的rejected狀態(tài)
    resolve和reject都可以傳遞最多一個參數(shù), 表示推向狀態(tài)的數(shù)據(jù)
    */
})

promise.then(result => {
    /*
    這是thenable函數(shù), 如果當(dāng)前的Promise已經(jīng)是resolved狀態(tài), 該函數(shù)會立即執(zhí)行,如果當(dāng)前是未決狀態(tài), 則會加入作業(yè)隊列, 等待Promise狀態(tài)變?yōu)閞esolved會立即執(zhí)行
    */
}, err => {
    /*
    這是catchable函數(shù), 如果當(dāng)前的Promise已經(jīng)是rejected狀態(tài), 該函數(shù)會立即執(zhí)行, 如果當(dāng)前是未決狀態(tài), 則會加入作業(yè)隊列, 等待Promise狀態(tài)變?yōu)閞ejected會立即執(zhí)行
    */
})

同時Promise可以有多個then和catch并列進(jìn)行,適用于對同一個Promise的多種處理

promise.then(res => {
    console.log('這個res我要存入緩存')
})
promise.then(res => {
    console.log('這個res我要用來獲取新一輪的數(shù)據(jù)');
})

Promise的細(xì)節(jié)

  1. 未決階段的處理函數(shù)是同步的, 會立即執(zhí)行
const promise = new Promise((resolve, reject) => {
    console.log('helloworld, 我是未決階段的處理函數(shù)');
    /* 這個函數(shù)是同步的會立即執(zhí)行 */
})
  1. thenable和catchable函數(shù)是異步的, 就算是立即執(zhí)行, 也會放到event queue中等待執(zhí)行, 并且加入的是微任務(wù)
const promise = new Promise((resolve, reject) => {
    console.log('padding狀態(tài), 但是我要立馬將他推入resolve');
    resolve('hello');
})

setTimeout(() => {
    console.log('我是宏任務(wù)的計時器')
},0)

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

console.log('我是callstack的執(zhí)行棧任務(wù)');

/* 
執(zhí)行結(jié)果肯定是
padding狀態(tài), 但是我要立馬將他推入resolve 
我是callstack的執(zhí)行棧任務(wù)
hello
我是宏任務(wù)的計時器
*/ 

如果對js執(zhí)行機制還不是很清楚的話, 可以查看我寫的js執(zhí)行機制和ui多線程的博客

  1. promise.then可以只添加thenable函數(shù), promise.catch也可以只添加catchable函數(shù)
promise.then(resulte => {
    /* thenable */
}).catch(error => {
    /* catchable */
})
  1. 在未決階段的處理函數(shù)中, 如果發(fā)生未捕獲的錯誤, 會將狀態(tài)推向rejected, 并會被catchable捕獲,(比如請求數(shù)據(jù)的時候網(wǎng)斷了)
const promise = new Promise((resolve, reject) => {
    throw new Error('xxx'); // 會導(dǎo)致promise 直接觸發(fā)reject狀態(tài)
})
  1. 一旦狀態(tài)推向了已決階段, 無法再對狀態(tài)做任何更改
const promise = new Promise((resolve, reject) => {
    resolve();
    resolve(); //這個是無效的, 因為上面的結(jié)果已經(jīng)確定了,結(jié)果是不可逆轉(zhuǎn)的
})

Promise并沒有消除回調(diào), 只是讓回調(diào)變得可控

下面我們來看幾個Promise的實例:

  1. 小明表白女神事件, 小明發(fā)出表白申請,因為要看看小明是不是自己心儀的男生, 所以女神會思考三秒鐘以后才會告訴小明答應(yīng)還是不答應(yīng)
  /* 小明向女神表白, 如果隨機數(shù)大于0.1 則表白失敗, 反之表白成功 */
        const promise = new Promise((resolve, reject) => {
            console.log('此時為padding狀態(tài),該函數(shù)是同步會立即執(zhí)行');
            setTimeout(() => {
                if(Math.random() < 0.1) {
                    /* 表白成功啦, 同時代表事件已經(jīng)處理, 從未決階段變成已決階段, 整件事情已經(jīng)產(chǎn)生結(jié)果(小明獲得女神芳心), 并且這個結(jié)果是按照正常邏輯走下去的結(jié)果, 所以我們需要用到resolve將Promise狀態(tài)推向resolve狀態(tài)*/ 
                    resolve(true);
                }else {
                    /* 表白失敗, 同樣代表事件已經(jīng)處理, 從未決階段變成已決階段, 整件事情已經(jīng)產(chǎn)生結(jié)果(小明心碎了), 這個結(jié)果也是按照正常邏輯走下去的結(jié)果(女神本來就可能同意也可能拒絕), 所以我們同樣需要用到resolve將Promise狀態(tài)推向resolve狀態(tài), 但是根據(jù)需求我們可能傳遞的參數(shù)不一樣*/ 
                    resolve(false);
                }
            }, 3000)
        })

        promise.then(result => {
            /*當(dāng)事件處于resolved狀態(tài)就會進(jìn)入thenable, 我們看到無論女神答應(yīng)與否其實這個都是按照正常邏輯走下去的狀態(tài), 我們通過then接受到這個狀態(tài), 同時根據(jù)不同的參數(shù)處理不同的結(jié)果*/
            console.log(result);
            if(result) {
                console.log('小明表白成功, 恭喜恭喜')
            }else {
                console.log('今天的我你愛理不理, 明天的我你高攀不起')
            }
        }).catch(err => {
            /*當(dāng)事情進(jìn)入了rejected狀態(tài)的話會觸發(fā)catchable, 代表發(fā)生了一些錯誤, 比如網(wǎng)絡(luò)請求失敗*/
            console.log(err);
        })
  1. 在網(wǎng)絡(luò)請求中使用Promise來請求數(shù)據(jù)
  /* 在網(wǎng)絡(luò)請求中使用Promise */
        const ajaxPromise = new Promise((resolve, reject) => {
            /* 開啟ajax請求 */
            ajax({
                url: 'www.xxx.com',
                method: 'POST',
                data: {
                    name: 'tommy'
                },
                /* 在成功的回調(diào)函數(shù)中, 不管返回的結(jié)果是什么我們都會用resolve將Promise的狀態(tài)推入resolved狀態(tài) */
                success: function(data) {
                    resolve(data);
                },
                fail: function(err) {
                /* 而如果觸發(fā)了失敗的回調(diào)函數(shù), 那么可能是網(wǎng)絡(luò)或者服務(wù)器或者自身出問題了,我們應(yīng)該將狀態(tài)推入rejected狀態(tài) */
                    rejected(err);
                }
            })
        })
        /* 只要Promise狀態(tài)變?yōu)閞esolved狀態(tài)就會立馬觸發(fā)thenabale的函數(shù), 只要變?yōu)閞ejected狀態(tài)馬上就會觸發(fā)catchable函數(shù) */
        ajaxPromise.then(data => {
            console.log(data);
            /* 拿到data做一系列處理 */
        }).catch(err => {
            console.log(err);
            /* 查看捕獲的錯誤 */
        })

Promise的串聯(lián)

當(dāng)后續(xù)的Promise需要用到之前的promise的處理結(jié)果時, 就需要用到promise的串聯(lián)

在promise對象中, 無論是then方法還是catch方法, 都是有返回值的,返回的是一個全新的promise對象, 他的狀態(tài)滿足下面的規(guī)則

  • 如果當(dāng)前的Promise是未決(padding)狀態(tài), 那么得到的新的Promise也是未決狀態(tài)
  • 如果當(dāng)前的Promise是已決狀態(tài), 那么會運行相應(yīng)的后續(xù)處理函數(shù), 并將后續(xù)處理函數(shù)的結(jié)果(返回值)作為resolved的狀態(tài)數(shù)據(jù), 應(yīng)用到新的Promise中, 如果后續(xù)處理函數(shù)發(fā)生錯誤, 則把返回值作為rejected的狀態(tài)數(shù)據(jù), 應(yīng)用到新的Promise中.
    后續(xù)的Promise一定會等到前面的Promise有了后續(xù)處理結(jié)果后才會變成已決狀態(tài)
const promise = new Promise((resolve, reject) => {
    resolve(1);
})
const promise2 = promise.then(res => res * 2);
promise2.then(res => {
    console.log(res);
})
/* 直接輸出promise2拿到的是padding狀態(tài)的promise對象, 因為then和catch都是異步的, 同時promise被resolve推向了resolved狀態(tài), 那么勢必會執(zhí)行then后面的函數(shù), promise2也會變?yōu)閞esolved狀態(tài), 同時promise的then的返回結(jié)果會作為promise2的resolved處理函數(shù)的參數(shù)被放進(jìn)then中, 也就是上面的res */

如果返回的是一個Promise對象, 那么會把這個Promise對象拆解開并且把狀態(tài)數(shù)據(jù)傳遞給新的Promise對象

const promise = new Promise((resolve, reject) => {
    resolve(1);
})
const promise2 = promise.then(res => {
    return new Promise((resolve, reject) => {
        resolve(2);
    })
})
promise2.then(res => {
    console.log(res); // 輸出2
})

Promise的其他api

原型成員(實例成員

  • then: 注冊一個后續(xù)處理函數(shù), 當(dāng)Promise為resolved狀態(tài)時運行該函數(shù),
  • catch: 注冊一個后續(xù)處理函數(shù), 當(dāng)Promise為rejected狀態(tài)時運行該函數(shù)
  • finally: [ES2018]注冊一個后續(xù)處理參數(shù)(無參), 當(dāng)Promise為已決時運行該函數(shù)
const promise = Promise((resolve, reject) => {
    resolve();
})

promise.finally(() => {
    console.log('helloworld');
})

構(gòu)造函數(shù)成員(靜態(tài)成員)

  • resolve(數(shù)據(jù)): 該方法返回一個resolved狀態(tài)的Promise, 傳遞的數(shù)據(jù)作為狀態(tài)數(shù)據(jù)丈咐。
    特殊情況: 如果傳遞的數(shù)據(jù)是Promise, 則直接返回傳遞的Promise對象
Promise.resolve(1); // 
  • reject(數(shù)據(jù)): 該方法返回一個rejected狀態(tài)的Promise, 傳遞的數(shù)據(jù)為狀態(tài)數(shù)據(jù)
Promise.reject(2); // 
  • all(iterable): 這個方法返回一個新的Promise對象, 該promise對象在iterable參數(shù)對象里所有的promise對象都成功的時候才會被觸發(fā)成功, 一旦有任何一個在iterable里的promise對象失敗則立馬觸發(fā)該promise對象的失敗, 這個新的promise對象在觸發(fā)成功狀態(tài)以后, 會把一個包含iterable里所有promise返回值的數(shù)組作為成功回調(diào)的返回值, 順序跟iterable保持一致,如果這個新的promise對象觸發(fā)了失敗狀態(tài), 那么他會把iterable里第一個觸發(fā)失敗的promise對象的失敗信息作為他的失敗錯誤信息, Promise.all方法常被用于處理多個promise對象的狀態(tài)集合。
    let proms = [];
    for(let i = 0; i < 10; i ++) {
        proms.push(new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(i);
            }, 3000)
        }))
    }
    console.log(proms);
    Promise.all(proms).then(res => {
        console.log('全部請求完成',res);
    }).catch(err => {
        console.log(err);
    })
  • race(iterable): 當(dāng)iterable參數(shù)里的任意一個子promise被成功或者失敗以后, 父類promise馬上也會用子promise的成功返回值或失敗詳情作為參數(shù)調(diào)用父類promise綁定的相對應(yīng)的句柄, 并返回該promise對象
let proms = [];
for(let i = 0; i < 10; i++) {
    proms.push(new Promise((resolve, reject) => {
        reject('我是第一個error' + i);
    }))
}

Promise.race(proms).then(res => {
    console.log(res);
}).catch(err => {
    console.log(err);
})
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載曹抬,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谤民,隨后出現(xiàn)的幾起案子堰酿,更是在濱河造成了極大的恐慌,老刑警劉巖张足,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件触创,死亡現(xiàn)場離奇詭異,居然都是意外死亡为牍,警方通過查閱死者的電腦和手機哼绑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碉咆,“玉大人抖韩,你說我怎么就攤上這事∫咄” “怎么了茂浮?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長壳咕。 經(jīng)常有香客問我席揽,道長,這世上最難降的妖魔是什么谓厘? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任幌羞,我火速辦了婚禮,結(jié)果婚禮上竟稳,老公的妹妹穿的比我還像新娘属桦。我一直安慰自己,他們只是感情好住练,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著愁拭,像睡著了一般讲逛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岭埠,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天盏混,我揣著相機與錄音,去河邊找鬼惜论。 笑死许赃,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的馆类。 我是一名探鬼主播混聊,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乾巧!你這毒婦竟也來了句喜?” 一聲冷哼從身側(cè)響起预愤,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咳胃,沒想到半個月后植康,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡展懈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年销睁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片存崖。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡冻记,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出金句,到底是詐尸還是另有隱情檩赢,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布违寞,位于F島的核電站贞瞒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏趁曼。R本人自食惡果不足惜军浆,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挡闰。 院中可真熱鬧乒融,春花似錦、人聲如沸摄悯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奢驯。三九已至申钩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瘪阁,已是汗流浹背撒遣。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留管跺,地道東北人义黎。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像豁跑,于是被迫代替她去往敵國和親廉涕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

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

  • Promise 對象 Promise 的含義 Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,698評論 1 56
  • JavaScript里通常不建議阻塞主程序火的,尤其是一些代價比較昂貴的操作壶愤,如查找數(shù)據(jù)庫,下載文件等操作馏鹤,應(yīng)該用異步...
    張歆琳閱讀 2,743評論 0 12
  • 1. Promise 的含義 所謂Promise征椒,簡單說就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個...
    ROBIN2015閱讀 483評論 0 0
  • Promise 的含義 一句話概括一下promise的作用:可以將異步操作以同步操作的流程表達(dá)出來湃累,避免了層層嵌套...
    雪萌萌萌閱讀 5,447評論 0 7
  • 原文地址:http://es6.ruanyifeng.com/#docs/promise Promise 的含義 ...
    AI云棧閱讀 863評論 0 7