漫談promise使用場景

深入理解promise

對于現(xiàn)在的前端同學(xué)來說你不同promise你都不好意思出門了蔓涧。對于前端同學(xué)來說promise已經(jīng)成為了我們的必備技能件已。

那么,下面我們就來說一說promise是什么元暴,它能幫助我們解決什么問題篷扩,我們應(yīng)該如何使用它?

這是我個人對promise的理解茉盏。歡迎吐槽 :)

Promise是什么

promise的意思是承諾鉴未,有的人翻譯為許愿枢冤,但它們代表的都是未實現(xiàn)的東西,等待我們接下來去實現(xiàn)铜秆。

Promise最早出現(xiàn)在commnjs淹真,隨后形成了Promise/A規(guī)范。在Promise這個技術(shù)中它本身代表以目前還不能使用的對象连茧,但可以在將來的某個時間點被調(diào)用核蘸。使用Promise我們可以用同步的方式寫異步代碼。其實Promise在實際的應(yīng)用中往往起到代理的作用啸驯。例如值纱,我們像我們發(fā)出請求調(diào)用服務(wù)器數(shù)據(jù),由于網(wǎng)絡(luò)延時原因坯汤,我們此時無法調(diào)用到數(shù)據(jù)虐唠,我們可以接著執(zhí)行其它任務(wù),等到將來某個時間節(jié)點服務(wù)器響應(yīng)數(shù)據(jù)到達(dá)客戶端惰聂,我們即可使用promise自帶的一個回調(diào)函數(shù)來處理數(shù)據(jù)疆偿。

Promise能幫我們解決什么痛點

JavaScript實現(xiàn)異步執(zhí)行,在Promise未出現(xiàn)前搓幌,我們通常是使用嵌套的回調(diào)函數(shù)來解決的杆故。但是使用回調(diào)函數(shù)來解決異步問題,簡單還好說溉愁,但是如果問題比較復(fù)雜处铛,我們將會面臨回調(diào)金字塔的問題(pyramid of Doom)。

var a = function() {
    console.log('a');
};

var b = function() {
    console.log('b');
};

var c = function() {
    for(var i=0;i<100;i++){
        console.log('c')
    }  
};

a(b(c())); // 100個c -> b -> a

我們要桉順序的執(zhí)行a拐揭,b撤蟆,c三個函數(shù),我們發(fā)現(xiàn)嵌套回調(diào)函數(shù)確實可以實現(xiàn)異步操作(在c函數(shù)中循環(huán)100次堂污,發(fā)現(xiàn)確實是先輸出100個c家肯,然后在輸出b,最后是a)盟猖。但是你發(fā)現(xiàn)沒這種實現(xiàn)可讀性極差讨衣,如果是幾十上百且回調(diào)函數(shù)異常復(fù)雜,那么代碼維護起來將更加麻煩式镐。

那么反镇,接下來我們看一下使用promise(promise的實例可以傳入兩個參數(shù)表示兩個狀態(tài)的回調(diào)函數(shù),第一個是resolve娘汞,必選參數(shù)歹茶;第二個是reject,可選參數(shù))的方便之處。

var promise = new Promise(function(resolve, reject){
    console.log('............');
    resolve(); // 這是promise的一個機制辆亏,只有promise實例的狀態(tài)變?yōu)閞esolved风秤,才會會觸發(fā)then回調(diào)函數(shù)
});

promise.then(function(){
    for(var i=0;i<100;i++) {
        console.log('c')
    }    
})
.then(function(){
    console.log('b')
})
.then(function(){
    console.log('a')
})

那么,為什么嵌套的回調(diào)函數(shù)這種JavaScript自帶實現(xiàn)異步機制不招人喜歡呢扮叨,因為它的可讀性差缤弦,可維護性差;另一方面就是我們熟悉了jQuery的鏈?zhǔn)秸{(diào)用彻磁。所以碍沐,相比起來我們會更喜歡Promise的風(fēng)格。

promise的3種狀態(tài)

上面提到了promise的 resolved 狀態(tài)衷蜓,那么累提,我們就來說一下promise的3種狀態(tài),未完成(unfulfilled)磁浇、完成(fulfilled)斋陪、失敗(failed)置吓。

在promise中我們使用resolved代表fulfilled无虚,使用rejected表示fail。

ES6的Promise有哪些特性

  1. promise的狀態(tài)只能從 未完成->完成, 未完成->失敗 且狀態(tài)不可逆轉(zhuǎn)衍锚。

  2. promise的異步結(jié)果友题,只能在完成狀態(tài)時才能返回,而且我們在開發(fā)中是根據(jù)結(jié)果來選擇來選擇狀態(tài)的戴质,然后根據(jù)狀態(tài)來選擇是否執(zhí)行then()度宦。

  3. 實例化的Promise內(nèi)部會立即執(zhí)行,then方法中的異步回調(diào)函數(shù)會在腳本中所有同步任務(wù)完成時才會執(zhí)行告匠。因此戈抄,promise的異步回調(diào)結(jié)果最后輸出。示例代碼如下:

var promise = new Promise(function(resolve, reject) {
  console.log('Promise instance');
  resolve();
});

promise.then(function() {
  console.log('resolved result');
});
for(var i=0;i<100;i++) {
console.log(i);
/*
Promise instance
1
2
3
...
99
100
resolved result
*/

上面的代碼執(zhí)行輸出結(jié)果的先后順序凫海,曾經(jīng)有人拿到這樣一個面試題問過我呛凶,所以,這個問題還是要注意的行贪。

resolve中可以接受另一個promise實例

resolve中接受另一個另一個對象的實例后,resolve本實例的返回狀態(tài)將會有被傳入的promise的返回狀態(tài)來取代模闲。

reject狀態(tài)替換實例建瘫,代碼如下:

const p1 = new Promise(function (resolve, reject) {
    cosole.log('2秒之后,調(diào)用返回p1的reject給p2');
    setTimeout(reject, 3000, new Error('fail'))
})

const p2 = new Promise(function (resolve, reject) {
    cosole.log('1秒之后尸折,調(diào)用p1');
    setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))

// fail

resolve狀態(tài)替換實例啰脚,代碼如下:

const p1 = new Promise(function (resolve, reject) {
    cosole.log('2秒之后,調(diào)用返回p1的resolve給p2');
    setTimeout(resolve, 3000, 'success')
})

const p2 = new Promise(function (resolve, reject) {
    cosole.log('1秒之后,調(diào)用p1');
    setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))

// success

注意:promise實例內(nèi)部的resolve也執(zhí)行的是異步回調(diào)橄浓,所以不管resolve放的位置靠前還是靠后粒梦,都要等內(nèi)部的同步函數(shù)執(zhí)行完畢,才會執(zhí)行resolve異步回調(diào)荸实。

new Promise((resolve, reject) => {
    console.log(1);
    resolve(2);
    console.log(3);
}).then(result => {
    console.log(result);
});
/*
1
3
2
*/

這個問題也在面試題中出現(xiàn)過匀们,所以,要牢記准给。

promise和ajax如何結(jié)合使用

function PromiseGet (url) {
    return new Promise( (resolve, reject) => {
        let xhr = new XMLHttpRequest()
        xhr.open('GET', url, true)
        xhr.onreadystatechange = function () {
            if (this.readyState === 4) {
                if (this.status === 200) {
                    resolve(this.responseText, this)
                } else {
                    let resJson = {
                        code: this.status,
                        response: this.response
                    }
                    reject(resJson, this)
                }
            }
        }
        xhr.send()
    })
}

我們發(fā)現(xiàn)用promise技術(shù)結(jié)合ajax泄朴,只是在promise實例中引入ajax,在ajax請求處理的結(jié)果中使用了resolve和reject狀態(tài)露氮。

前面我們說了resolve()祖灰,返回執(zhí)行then()代表完成,那么畔规,reject()代表失敗局扶,返回執(zhí)行catch(),同時運行中拋出的錯誤也會執(zhí)行catch()

現(xiàn)在有一個非常好用的promise和Ajax結(jié)合的github項目Axios 叁扫,想要深入了解的同學(xué)可以研究下它的源碼三妈。

promise.all()方法可以處理一個以promise實例為元素的數(shù)組

let promise = Promise.all([p1, p2, p3])

promise 的狀態(tài)由p1,p2陌兑,p3共同決定沈跨。當(dāng)它們都為resolve狀態(tài)時,promise狀態(tài)為true兔综,它們的返回值組成一個數(shù)組饿凛,傳遞給promise;它們只要有一個的狀態(tài)為reject软驰,就將該實例的返回值傳遞給promise

promise.race()方法也可以處理一個promise實例數(shù)組

但它和promise.all()不同涧窒,從字面意思上理解就是競速,那么理解起來上就簡單多了锭亏,也就是說在數(shù)組中的元素實例那個率先改變狀態(tài)纠吴,就向下傳遞誰的狀態(tài)和異步結(jié)果。

將一個普通對象轉(zhuǎn)化為Promise對象

在開發(fā)中我們經(jīng)常會遇到.ajax()的使用且會遇到.ajax()間的依賴使用慧瘤,由于兩個或多個$.ajax()間是同步的戴已,如果我們并排著寫實現(xiàn)不了依賴關(guān)系,所以锅减,我們往往使用嵌套糖儡,但是對于ajax這樣復(fù)雜的結(jié)構(gòu),嵌套不是個好辦法怔匣,我們需要先將代碼抽象提取握联,做一下封裝,代碼如下:

/*
url:地址
data:數(shù)據(jù),在函數(shù)內(nèi)部會轉(zhuǎn)化成json金闽。如果沒傳纯露,表示用GET方法;如果傳了代芜,表示用POST方法
*/
function ajax(url, data, callback) {
    $.ajax({
      url: url,
      type: data == null ? 'GET' : 'POST',
      dataType: "json",
      data: data == null ? '' : JSON.stringify(data),
      async: true,
      contentType: "application/json",
      success: function (data) {
          callback(data);
      },
      error: function (XMLHttpRequest, textStatus) {
        if (XMLHttpRequest.status == "401") {
            window.parent.location = '...';
            self.location = '...';
        } else {
            alert(XMLHttpRequest.responseText);
        }
      }
    });
}

那么埠褪,我們應(yīng)該如何避免回調(diào)金字塔呢?很顯然蜒犯,我們可以將它與promise結(jié)合组橄,代碼如下:

function ajax(url, data, callback) =>
    new Promise((resolve, reject) => {
        $.ajax({
            url: url,
            type: data == null ? 'GET' : 'POST',
            dataType: "json",
            data: data == null ? '' : JSON.stringify(data),
            async: true,
            contentType: "application/json",
            success: function (data) {
                callback(data);
                resolve();
            },
            error: function (XMLHttpRequest, textStatus) {
                if (XMLHttpRequest.status == "401") {
                    window.parent.location = '...'
                    self.location = '...'
                } else {
                    alert(XMLHttpRequest.responseText);
                }
                reject()
            }
        })
    })  
}

當(dāng)然,這是不熟悉jQuery的同學(xué)罚随,或者考慮長線Promise的玉工,但是jQuery也為我們提供了按順序調(diào)用多個$.ajax()的方案,那就是deferred淘菩,它模擬了promise的實現(xiàn)遵班,有興趣的同學(xué)可以查看源碼,看它是如何實現(xiàn)的潮改。實例代碼如下:

$.ajax({
    url:'./a'
}).then(function(){
    return $.ajax({ url:'./b' });
}).then(function(){
    return $.ajax({ url:'./c' });
}).then(function(){
    return $.ajax({ url:'./d' });
}).then(function(){
    //TODO here
});

promise存在的問題

  • promise一旦執(zhí)行狭郑,無法中途取消
  • promise的錯誤無法在外部被捕捉到,只能在內(nèi)部進行預(yù)判處理
  • promise的內(nèi)如何執(zhí)行汇在,監(jiān)測起來很難

正是因為這些原因劣光,ES7引入了更加靈活多變的async器净,await來處理異步抱虐。

這個稍后驹针,后續(xù)可能還會繼續(xù)修改,也歡迎各位批評指正阿蝶。有問題或者有其他想法的可以在我的GitHub上pr雳锋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市羡洁,隨后出現(xiàn)的幾起案子玷过,更是在濱河造成了極大的恐慌,老刑警劉巖筑煮,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辛蚊,死亡現(xiàn)場離奇詭異,居然都是意外死亡真仲,警方通過查閱死者的電腦和手機嚼隘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來袒餐,“玉大人,你說我怎么就攤上這事【难郏” “怎么了卧檐?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長焰宣。 經(jīng)常有香客問我霉囚,道長,這世上最難降的妖魔是什么匕积? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任盈罐,我火速辦了婚禮,結(jié)果婚禮上闪唆,老公的妹妹穿的比我還像新娘盅粪。我一直安慰自己,他們只是感情好悄蕾,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布票顾。 她就那樣靜靜地躺著,像睡著了一般帆调。 火紅的嫁衣襯著肌膚如雪奠骄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天番刊,我揣著相機與錄音含鳞,去河邊找鬼。 笑死芹务,一個胖子當(dāng)著我的面吹牛蝉绷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锄禽,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼潜必,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了沃但?” 一聲冷哼從身側(cè)響起磁滚,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宵晚,沒想到半個月后垂攘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡淤刃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年晒他,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逸贾。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡陨仅,死狀恐怖津滞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情灼伤,我是刑警寧澤触徐,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站狐赡,受9級特大地震影響撞鹉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颖侄,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一鸟雏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧览祖,春花似錦孝鹊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至玄货,卻和暖如春皇钞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背松捉。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工夹界, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人隘世。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓可柿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親丙者。 傳聞我的和親對象是個殘疾皇子复斥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361

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

  • Promise 對象 Promise 的含義 Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,711評論 1 56
  • Promiese 簡單說就是一個容器械媒,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果目锭,語法上說,Pr...
    雨飛飛雨閱讀 3,361評論 0 19
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持纷捞,譯者再次奉上一點點福利:阿里云產(chǎn)品券痢虹,享受所有官網(wǎng)優(yōu)惠,并抽取幸運大...
    HetfieldJoe閱讀 11,028評論 26 95
  • 00、前言Promise 是異步編程的一種解決方案糜值,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強大丰捷。它由社區(qū)...
    夜幕小草閱讀 2,135評論 0 12
  • “五一”小長假期間坯墨,在市局的統(tǒng)一部署下,特警支隊高度重視瓢阴,節(jié)前徐易支隊長主持專門召開“五一”安保動員會議畅蹂,要...
    江安_a776閱讀 1,359評論 2 0