Promise詳解

promise簡(jiǎn)介

Promise的出現(xiàn),原本是為了解決回調(diào)地獄的問題。所有人在講解Promise時(shí),都會(huì)以一個(gè)ajax請(qǐng)求為例,此處我們也用一個(gè)簡(jiǎn)單的ajax的例子來帶大家看一下Promise是如何使用的。

ajax請(qǐng)求的傳統(tǒng)寫法:

getData(method, url, successFun, failFun){
  var xmlHttp = new XMLHttpRequest();
  xmlHttp.open(method, url);
  xmlHttp.send();
  xmlHttp.onload = function () {
    if (this.status == 200 ) {
      successFun(this.response);
    } else {
      failFun(this.statusText);
    }
  };
  xmlHttp.onerror = function () {
    failFun(this.statusText);
  };
}

改為promise后的寫法:

getData(method, url){
  var promise = new Promise(function(resolve, reject){
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open(method, url);
    xmlHttp.send();
    xmlHttp.onload = function () {
      if (this.status == 200 ) {
        resolve(this.response);
      } else {
        reject(this.statusText);
      }
    };
    xmlHttp.onerror = function () {
      reject(this.statusText);
    };
  })
  return promise;
}

getData('get','www.xxx.com').then(successFun, failFun)

很顯然,我們把異步中使用回調(diào)函數(shù)的場(chǎng)景改為了.then()、.catch()等函數(shù)鏈?zhǔn)秸{(diào)用的方式?;趐romise我們可以把復(fù)雜的異步回調(diào)處理方式進(jìn)行模塊化。

下面,我們就來介紹一下Promise到底是個(gè)什么東西?它是如何做到的?

Promise的狀態(tài)

其實(shí)promise原理說起來并不難,它內(nèi)部有三個(gè)狀態(tài),分別是pending,fulfilled和rejected

pending是對(duì)象創(chuàng)建后的初始狀態(tài),當(dāng)對(duì)象fulfill(成功)時(shí)變?yōu)閒ulfilled,當(dāng)對(duì)象reject(失?。r(shí)變?yōu)閞ejected。且只能從pengding變?yōu)閒ulfilled或rejected ,而不能逆向或從fulfilled變?yōu)閞ejected 、從rejected變?yōu)閒ulfilled。如圖所示:


Promise實(shí)例方法介紹

Promise對(duì)象擁有兩個(gè)實(shí)例方法then()和catch()

從前面的例子中可以看到,成功和失敗的回調(diào)函數(shù)我們是通過then()添加,在promise狀態(tài)改變時(shí)分別調(diào)用。promise構(gòu)造函數(shù)中通常都是異步的,所以then方法往往都先于resolve和reject方法執(zhí)行。所以promise內(nèi)部需要有一個(gè)存儲(chǔ)fulfill時(shí)調(diào)用函數(shù)的數(shù)組和一個(gè)存儲(chǔ)reject時(shí)調(diào)用函數(shù)的數(shù)組。

從上面的例子中我們還可以看到then方法可以接收兩個(gè)參數(shù),且通常都是函數(shù)(非函數(shù)時(shí)如何處理下一篇文章中會(huì)詳細(xì)介紹)。第一個(gè)參數(shù)會(huì)添加到fulfill時(shí)調(diào)用的數(shù)組中,第二個(gè)參數(shù)添加到reject時(shí)調(diào)用的數(shù)組中。當(dāng)promise狀態(tài)fulfill時(shí),會(huì)把resolve(value)中的value值傳給調(diào)用的函數(shù)中,同理,當(dāng)promise狀態(tài)reject時(shí),會(huì)把reject(reason)中的reason值傳給調(diào)用的函數(shù)。例:

var p = new Promise(function(resolve, reject){
    resolve(5)
}).then(function(value){
    console.log(value) //5
})

var p1 = new Promise(function(resolve, reject){
    reject(new Error('錯(cuò)誤'))
}).then(function(value){
    console.log(value)
}, function(reason){
    console.log(reason) //Error: 錯(cuò)誤(…)
})

then方法會(huì)返回一個(gè)新的promise,下面的例子中p == p1將返回false,說明p1是一個(gè)全新的對(duì)象。

var p = new Promise(function(resolve, reject){
    resolve(5)
})
var p1 = p.then(function(value){
    console.log(value)
})
p == p1 // false

這也是為什么then是可以鏈?zhǔn)秸{(diào)用的,它是在新的對(duì)象上添加成功或失敗的回調(diào),這與jQuery中的鏈?zhǔn)秸{(diào)用不同。

那么新對(duì)象的狀態(tài)是基于什么改變的呢?是不是說如果p的狀態(tài)fulfill,后面的then創(chuàng)建的新對(duì)象都會(huì)成功;或者說如果p的狀態(tài)reject,后面的then創(chuàng)建的新對(duì)象都會(huì)失???

var p = new Promise(function(resolve, reject){
    resolve(5)
})
var p1 = p.then(function(value){
    console.log(value)   // 5
}).then(function(value){
    console.log('fulfill ' + value)   // fulfill undefined
}, function(reason){
    console.log('reject ' + reason)   
})

上面的例子會(huì)打印出5和"fulfill undefined"說明它的狀態(tài)變?yōu)槌晒?。那如果我們?cè)趐1的then方法中拋出異常呢?

var p = new Promise(function(resolve, reject){
    resolve(5)
})
var p1 = p.then(function(value){
    console.log(value)   // 5
    throw new Error('test')
}).then(function(value){
    console.log('fulfill ' + value)
}, function(reason){
    console.log('reject ' + reason)   // reject Error: test
})

理所當(dāng)然,新對(duì)象肯定會(huì)失敗。

反過來如果p失敗了,會(huì)是什么樣的呢?

var p = new Promise(function(resolve, reject){
    reject(5)
})
var p1 = p.then(undefined, function(value){
    console.log(value)   // 5
}).then(function(value){
    console.log('fulfill ' + value)   // fulfill undefined
}, function(reason){
    console.log('reject ' + reason)
})

說明新對(duì)象狀態(tài)不會(huì)受到前一個(gè)對(duì)象狀態(tài)的影響。

再來看如下代碼:

var p = new Promise(function(resolve, reject){
    reject(5)
})
var p1 = p.then(function(value){
    console.log(value) 
})
var p2 = p1.then(function(value){
    console.log('fulfill ' + value)
}, function(reason){
    console.log('reject ' + reason)   // reject 5
})

我們發(fā)現(xiàn)p1的狀態(tài)變?yōu)閞ejected,從而觸發(fā)了then方法第二個(gè)參數(shù)的函數(shù)。這似乎與我們之前提到的有差異啊,p1的狀態(tài)受到了p的狀態(tài)的影響。

再來看一個(gè)例子:

var p = new Promise(function(resolve, reject){
    resolve(5)
})
var p1 = p.then(undefined, function(value){
    console.log(value) 
})
var p2 = p1.then(function(value){
    console.log('fulfill ' + value)   // fulfill 5
}, function(reason){
    console.log('reject ' + reason)   
})

細(xì)心的人可能會(huì)發(fā)現(xiàn),該例子中then第一個(gè)參數(shù)是undefined,且value值5被傳到了p1成功時(shí)的回調(diào)函數(shù)中。上面那個(gè)例子中then的第二個(gè)參數(shù)是undefined,同樣reason值也傳到了p1失敗時(shí)的回調(diào)函數(shù)中。這是因當(dāng)對(duì)應(yīng)的參數(shù)不為函數(shù)時(shí),會(huì)將前一promise的狀態(tài)和值傳遞下去。

promise含有一個(gè)實(shí)例方法catch,從名字上我們就看得出來,它和異常有千絲萬縷的關(guān)系。其實(shí)catch(onReject)方法等價(jià)于then(undefined, onReject),也就是說如下兩種情況是等效的。

new Promise(function(resolve, reject){
    reject(new Error('error'))
}).then(undefined, function(reason){
    console.log(reason) // Error: error(…)
})

new Promise(function(resolve, reject){
    reject(new Error('error'))
}).catch(function(reason){
    console.log(reason) // Error: error(…)
})

我們提到參數(shù)不為函數(shù)時(shí)會(huì)把值和狀態(tài)傳遞下去。所以我們可以在多個(gè)then之后添加一個(gè)catch方法,這樣前面只要reject或拋出異常,都會(huì)被最后的catch方法處理。

new Promise(function(resolve, reject){
    resolve(5)
}).then(function(value){
    taskA()
}).then(function(value){
    taskB()
}).then(function(value){
    taskC()
}).catch(function(reason){
    console.log(reason)
})

Promise的靜態(tài)方法

Promise還有四個(gè)靜態(tài)方法,分別是resolve、reject、all、race,下面我們一一介紹。

除了通過new Promise()的方式,我們還有兩種創(chuàng)建Promise對(duì)象的方法:
Promise.resolve() 它相當(dāng)于創(chuàng)建了一個(gè)立即resolve的對(duì)象。如下兩段代碼作用相同:

Promise.resolve(5)

new Promise(function(resolve){
    resolve(5)
})

它使得promise對(duì)象直接resolve,并把5傳到后面then添加的成功函數(shù)中。

Promise.resolve(5).then(function(value){
    console.log(value) // 5
})

Promise.reject() 很明顯它相當(dāng)于創(chuàng)建了一個(gè)立即reject的對(duì)象。如下兩段代碼作用相同:

Promise.reject(new Error('error'))

new Promise(function(resolve, reject){
    reject(new Error('error'))
})

它使得promise對(duì)象直接reject,并把error傳到后面catch添加的函數(shù)中

Promise.reject(new Error('error')).catch(function(reason){
    console.log(reason) // Error: error(…)
})

Promise.all() 它接收一個(gè)promise對(duì)象組成的數(shù)組作為參數(shù),并返回一個(gè)新的promise對(duì)象。

當(dāng)數(shù)組中所有的對(duì)象都resolve時(shí),新對(duì)象狀態(tài)變?yōu)閒ulfilled,所有對(duì)象的resolve的value依次添加組成一個(gè)新的數(shù)組,并以新的數(shù)組作為新對(duì)象resolve的value,例:

Promise.all([Promise.resolve(5), 
  Promise.resolve(6), 
  Promise.resolve(7)]).then(function(value){
    console.log('fulfill', value)  // fulfill [5, 6, 7]
}, function(reason){
    console.log('reject',reason)
})

當(dāng)數(shù)組中有一個(gè)對(duì)象reject時(shí),新對(duì)象狀態(tài)變?yōu)閞ejected,并以當(dāng)前對(duì)象reject的reason作為新對(duì)象reject的reason。

Promise.all([Promise.resolve(5), 
  Promise.reject(new Error('error')), 
  Promise.resolve(7),
  Promise.reject(new Error('other error'))
  ]).then(function(value){
    console.log('fulfill', value)
}, function(reason){
    console.log('reject', reason)  // reject Error: error(…)
})

那當(dāng)數(shù)組中,傳入了非promise對(duì)象會(huì)如何呢?

Promise.all([Promise.resolve(5), 
  6,
  true,
  'test',
  undefined,
  null,
  {a:1},
  function(){},
  Promise.resolve(7)
  ]).then(function(value){
    console.log('fulfill', value)  // fulfill [5, 6, true, "test", undefined, null, Object, function, 7]
}, function(reason){
    console.log('reject', reason)
})

我們發(fā)現(xiàn),當(dāng)傳入的值為數(shù)字、boolean、字符串、undefined、null、{a:1}、function(){}等非promise對(duì)象時(shí),會(huì)依次把它們添加到新對(duì)象resolve時(shí)傳遞的數(shù)組中。

那數(shù)組中的多個(gè)對(duì)象是同時(shí)調(diào)用,還是一個(gè)接一個(gè)的依次調(diào)用呢?我們?cè)倏磦€(gè)例子

function timeout(time) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(time);
        }, time);
    });
}
console.time('promise')
Promise.all([
    timeout(10),
    timeout(60),
    timeout(100)
]).then(function (values) {
    console.log(values); [10, 60, 100]
    console.timeEnd('promise');   // 107ms 
});

由此我們可以看出,傳入的多個(gè)對(duì)象幾乎是同時(shí)執(zhí)行的,因?yàn)榭偟臅r(shí)間略大于用時(shí)最長的一個(gè)對(duì)象resolve的時(shí)間。

Promise.race() 它同樣接收一個(gè)promise對(duì)象組成的數(shù)組作為參數(shù),并返回一個(gè)新的promise對(duì)象。

與Promise.all()不同,它是在數(shù)組中有一個(gè)對(duì)象(最早改變狀態(tài))resolve或reject時(shí),就改變自身的狀態(tài),并執(zhí)行響應(yīng)的回調(diào)。

Promise.race([Promise.resolve(5), 
  Promise.reject(new Error('error')), 
  Promise.resolve(7)]).then(function(value){
    console.log('fulfill', value)  // fulfill 5
}, function(reason){
    console.log('reject',reason)
})

Promise.race([Promise.reject(new Error('error')), 
  Promise.resolve(7)]).then(function(value){
    console.log('fulfill', value) 
}, function(reason){
    console.log('reject',reason) //reject Error: error(…)
})

且當(dāng)數(shù)組中有非異步Promise對(duì)象或有數(shù)字、boolean、字符串、undefined、null、{a:1}、function(){}等非Promise對(duì)象時(shí),都會(huì)直接以該值resolve。

Promise.race([new Promise((resolve)=>{
    setTimeout(()=>{
        resolve(1)
    },100)}),
  Promise.resolve(5), 
  "test",
  Promise.reject(new Error('error')), 
  Promise.resolve(7)]).then(function(value){
    console.log('fulfill', value)  // fulfill 5
}, function(reason){
    console.log('reject',reason)
})
// fulfill 5

數(shù)組中第一個(gè)元素是異步的Promise,第二個(gè)是非異步Promise,會(huì)立即改變狀態(tài),所以新對(duì)象會(huì)立即改變狀態(tài)并把5傳遞給成功時(shí)的回調(diào)函數(shù)。

那么問題又來了,既然數(shù)組中第一個(gè)元素成功或失敗就會(huì)改變新對(duì)象的狀態(tài),那數(shù)組中后面的對(duì)象是否會(huì)執(zhí)行呢?

function timeout(time) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            console.log(time)
            resolve(time);
        }, time);
    });
}
console.time('promise')
Promise.race([
    timeout(10),
    timeout(60),
    timeout(100)
]).then(function (values) {
    console.log(values); [10, 60, 100]
    console.timeEnd('promise');   // 107ms
});

// 結(jié)果依次為
// 10
// 10
// promise: 11.1ms
// 60
// 100

說明即使新對(duì)象的狀態(tài)改變,數(shù)組中后面的promise對(duì)象還會(huì)執(zhí)行完畢,其實(shí)Promise.all()中即使前面reject了,所有的對(duì)象也都會(huì)執(zhí)行完畢。規(guī)范中,promise對(duì)象執(zhí)行是不可以中斷的。

補(bǔ)充

promise對(duì)象即使立馬改變狀態(tài),它也是異步執(zhí)行的。如下所示:

Promise.resolve(5).then(function(value){
  console.log('后打出來', value)
});
console.log('先打出來')

// 結(jié)果依次為
// 先打出來
// 后打出來 5

但還有一個(gè)有意思的例子,如下:

setTimeout(function(){console.log(4)},0);
new Promise(function(resolve){
    console.log(1)
    for( var i=0 ; i<10000 ; i++ ){
        i==9999 && resolve()
    }
    console.log(2)
}).then(function(){
    console.log(5)
});
console.log(3);

結(jié)果是 1 2 3 5 4,命名4是先添加到異步隊(duì)列中的,為什么結(jié)果不是1 2 3 4 5呢?這個(gè)涉及到Event loop,我之前文章講過。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Promise 對(duì)象 Promise 的含義 Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函...
    neromous閱讀 8,823評(píng)論 1 56
  • 在ES6當(dāng)中添加了很多新的API其中很值得一提的當(dāng)然少不了Promise,因?yàn)镻romise的出現(xiàn),很輕松的就給開...
    嘿_那個(gè)誰閱讀 3,744評(píng)論 2 3
  • 一、Promise的含義 Promise在JavaScript語言中早有實(shí)現(xiàn),ES6將其寫進(jìn)了語言標(biāo)準(zhǔn),統(tǒng)一了用法...
    Alex灌湯貓閱讀 887評(píng)論 0 2
  • Promise 是一種處理異步的思路。Promise 也是一個(gè)類。當(dāng)我們說一個(gè) promise 的時(shí)候,一般指一個(gè)...
    黃昏少年閱讀 1,176評(píng)論 0 0
  • 恍惚間天是越來越冷了,昨晚看完表演,心里又是莫名的難受,路上好冷,我裹緊了衣服。走到體育館的臺(tái)階上坐著,坐了好久,...
    云深鋆深閱讀 325評(píng)論 0 5

友情鏈接更多精彩內(nèi)容