ES6-Promise對(duì)象 (上)

前言

在Promise之前,js的異步編程都是采用回調(diào)函數(shù)和事件的方式。但是這種編程方式在處理復(fù)雜業(yè)務(wù)的情況下,很容易出現(xiàn)callback hell(回調(diào)地獄),使得代碼很難被理解和維護(hù)。Promise就是改善這種情形的異步編程的解決方案,它由社區(qū)最早提出和實(shí)現(xiàn),es6將其寫進(jìn)了語(yǔ)言標(biāo)準(zhǔn),統(tǒng)一了用法,并且提供了一個(gè)原生的對(duì)象Promise。

但是在這之前,大家想要使用Promise,一般會(huì)借助于第三方庫(kù),或者當(dāng)你知道其中的原理以后,也可以手動(dòng)實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Promise.當(dāng)然,為了防止不可預(yù)知的bug,在生產(chǎn)項(xiàng)目中最好還是不要使用原生的或者自己編寫的Promise(目前為止并不是所有瀏覽器都能很好的兼容ES6),而是使用已經(jīng)較為成熟的有大量小伙伴使用的第三方Promise庫(kù)?,F(xiàn)今流行的各大js庫(kù),幾乎都不同程度的實(shí)現(xiàn)了Promise,如jQuery、Zepto等,只是暴露出來(lái)的大都是Deferred對(duì)象,當(dāng)然還有angularJs中的$q。

注:以下所有的測(cè)試代碼請(qǐng)?jiān)诟呒?jí)瀏覽器或node環(huán)境下運(yùn)行
我們還是先來(lái)看看Promise的真身

console.log(new Promise(
    function(resolve,reject){ })
);

打開(kāi)瀏覽器的控制臺(tái)我們可以看到:

Promise.png

從控制臺(tái)輸出的Promise對(duì)象我們可以清楚的看到Promise對(duì)象有以下幾種基本方法:
Promise.resolve()
Promise.reject()
Promise.all()
Promise.race()
Promise.prototype.then()
Promise.prototype.catch()
更多點(diǎn)擊Promise官網(wǎng)API,我們先不著急記住他們,只要對(duì)Promise對(duì)象有個(gè)整體的認(rèn)識(shí)。


1.Promise對(duì)象狀態(tài)

  • pending: 初始狀態(tài), 既不是 fulfilled 也不是 rejected.
  • fulfilled: 成功的操作.
  • rejected: 失敗的操作.
    pending狀態(tài)的Promise對(duì)象既可轉(zhuǎn)換為帶著一個(gè)成功值的fulfilled狀態(tài),也可變?yōu)閹е粋€(gè)失敗信息的 rejected狀態(tài)。當(dāng)狀態(tài)發(fā)生轉(zhuǎn)換時(shí),Promise.then綁定的方法就會(huì)被調(diào)用。(當(dāng)綁定方法時(shí),如果 Promise對(duì)象已經(jīng)處于fulfilled或rejected狀態(tài),那么相應(yīng)的方法將會(huì)被立刻調(diào)用,所以在異步操作的完成情況和它的綁定方法之間不存在競(jìng)爭(zhēng)條件。)
    因?yàn)镻romise.prototype.then和Promise.prototype.catch方法返回Promises對(duì)象, 所以它們可以被鏈?zhǔn)秸{(diào)用。
Promise對(duì)象活動(dòng)流程.png

注意,Promise狀態(tài)的改變只會(huì)出現(xiàn)從未完成態(tài)向完成態(tài)或失敗態(tài)轉(zhuǎn)化,不能逆反。完成態(tài)和失敗態(tài)不能互相轉(zhuǎn)化,而且,狀態(tài)一旦轉(zhuǎn)化,將不能更改。


2.基本用法

(1)constructor

語(yǔ)法

new Promise(executor);
new Promise(function(resolve, reject) { ... });

參數(shù)

name desc
executor 帶有resolve、reject兩個(gè)參數(shù)的函數(shù)對(duì)象。第一個(gè)參數(shù)用在處理執(zhí)行成功的場(chǎng)景,第二個(gè)參數(shù)則用在處理執(zhí)行失敗的場(chǎng)景。一旦我們的操作完成即可調(diào)用這些函數(shù)。

ES6 的 Promise 對(duì)象是一個(gè)構(gòu)造函數(shù),用來(lái)生成 Promise 實(shí)例。

var promise = new Promise(function(resolve, reject) {
  if (/* 異步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

上面代碼中,Promise 構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù),該函數(shù)的兩個(gè)參數(shù)分別是 resolve 方法和 reject 方法。如果異步操作成功,則用 resolve 方法將 Promise 對(duì)象的狀態(tài),從“未完成”變?yōu)椤俺晒Α保磸?pending 變?yōu)?resolved);如果異步操作失敗,則用 reject 方法將 Promise 對(duì)象的狀態(tài),從“未完成”變?yōu)椤笆 保磸?pending 變?yōu)?rejected)。
Promise 實(shí)例生成以后,可以用 then 方法分別指定 resolve 方法和 reject 方法的回調(diào)函數(shù)。

這里我們要特別注意兩點(diǎn):
1?? Promise 新建后就會(huì)立即執(zhí)行。
我們來(lái)一段代碼測(cè)試一下:

var p1=new Promise(function(res,rej){
    setTimeout(()=>{res("p1 end!");},1200);
    console.log('我先執(zhí)行');
})

p1.then(function(data){
    console.log(data);
})

控制臺(tái)輸出:
我先執(zhí)行
p1 end!

2?? 如果調(diào)用 resolve 方法和 reject 方法時(shí)帶有參數(shù),那么它們的參數(shù)會(huì)被傳遞給回調(diào)函數(shù)。reject 方法的參數(shù)通常是 Error 對(duì)象的實(shí)例,表示拋出的錯(cuò)誤;resolve 方法的參數(shù)除了正常的值以外,還可能是另一個(gè) Promise 實(shí)例,表示異步操作的結(jié)果有可能是一個(gè)值,也有可能是另一個(gè)異步操作,比如像下面這樣:

var p1 = new Promise(function(resolve, reject){
  // ...
});
var p2 = new Promise(function(resolve, reject){
  // ...
  resolve(p1);
})

注意,這時(shí)p1的狀態(tài)就會(huì)傳遞給p2,也就是說(shuō),p1的狀態(tài)決定了p2的狀態(tài)。如果p1的狀態(tài)是pending,那么p2的回調(diào)函數(shù)就會(huì)等待p1的狀態(tài)改變;如果p1的狀態(tài)已經(jīng)是resolved或者rejected,那么p2的回調(diào)函數(shù)將會(huì)立刻執(zhí)行。

var p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000);
  console.log('p1p1p1');
})

var p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000);
  console.log('p2p2p2');
})

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

執(zhí)行結(jié)果如下:


res.png

這里也再次說(shuō)明第一個(gè)注意點(diǎn)提到的Promise 新建后就會(huì)立即執(zhí)行;

(2)Promise.prototype.then()

在實(shí)例化一個(gè)Promise對(duì)象之后,我們調(diào)用該對(duì)象實(shí)例的then()方法為實(shí)例添加狀態(tài)改變時(shí)的回調(diào)函數(shù):

  • 第一個(gè)參數(shù)(函數(shù))是resolved狀態(tài)的回調(diào)函數(shù)
  • 第二個(gè)參數(shù)(函數(shù))是rejected狀態(tài)的回調(diào)函數(shù)

我們做一個(gè)異步加載圖片實(shí)際案例來(lái)小試牛刀

function loadImageAsync(url) {
    return new Promise(function (reslove, reject) {
        var img = new Image();
        img.onload = function () {
            reslove();
        }
        img.onerror = function () {
            reject();
        }
        console.log("loading image");
        img.src = url;
    });
}
var loadImage1 = loadImageAsync("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1510427875428&di=0df1bd50978bba9e0c0ba75e325b956a&imgtype=0&src=http%3A%2F%2Fimg.zybus.com%2Fuploads%2Fallimg%2F141110%2F1-141110163F6.jpg"); //來(lái)自百度的梅西圖片
loadImage1.then(function success() {
    console.log("loadImage1 load success");
}, function fail() {
    console.log("loadImage1 load fail");
});

var loadImage2 = loadImageAsync("http://127.0.0.1/upload/patty.png");  //這張圖片不存在
loadImage2.then(function success() {
    console.log("loadImage2 load success");
}, function fail() {
    console.log("loadImage2 load fail");
});

我們看看控制臺(tái)發(fā)生了什么:


loadPhoto.png
返回值

Promise.prototype.then 方法返回的是一個(gè)新的Promise對(duì)象,因此可以采用鏈?zhǔn)綄懛?,即then方法后面再調(diào)用另一個(gè)then方法。

then方法返回的新的Promise對(duì)象的行為與then中的回調(diào)函數(shù)的返回值有關(guān):

  • 如果then中的回調(diào)函數(shù)返回一個(gè)值,那么then返回的Promise將會(huì)成為接受狀態(tài),并且將返回的值作為接受狀態(tài)的回調(diào)函數(shù)的參數(shù)值。
  • 如果then中的回調(diào)函數(shù)拋出一個(gè)錯(cuò)誤,那么then返回的Promise將會(huì)成為拒絕狀態(tài),并且將拋出的錯(cuò)誤作為拒絕狀態(tài)的回調(diào)函數(shù)的參數(shù)值。
  • 如果then中的回調(diào)函數(shù)返回一個(gè)已經(jīng)是接受狀態(tài)的Promise,那么then返回的Promise也會(huì)成為接受狀態(tài),并且將那個(gè)Promise的接受狀態(tài)的回調(diào)函數(shù)的參數(shù)值作為該被返回的Promise的接受狀態(tài)回調(diào)函數(shù)的參數(shù)值。
  • 如果then中的回調(diào)函數(shù)返回一個(gè)已經(jīng)是拒絕狀態(tài)的Promise,那么then返回的Promise也會(huì)成為拒絕狀態(tài),并且將那個(gè)Promise的拒絕狀態(tài)的回調(diào)函數(shù)的參數(shù)值作為該被返回的Promise的拒絕狀態(tài)回調(diào)函數(shù)的參數(shù)值。
  • 如果then中的回調(diào)函數(shù)返回一個(gè)未定狀態(tài)(pending)的Promise,那么then返回Promise的狀態(tài)也是未定的,并且它的終態(tài)與那個(gè)Promise的終態(tài)相同;同時(shí),它變?yōu)榻K態(tài)時(shí)調(diào)用的回調(diào)函數(shù)參數(shù)與那個(gè)Promise變?yōu)榻K態(tài)時(shí)的回調(diào)函數(shù)的參數(shù)是相同的。

是不是看著有一種迷糊的感覺(jué),我們這里只看第一種情況。

var p2 = new Promise(function(resolve, reject) {
  resolve(1);
});

p2.then(function(value) {
  console.log(value);   //1
  return value + 1;      //then中的回調(diào)函數(shù)返回一個(gè)值
}).then(function(value) {
  console.log(value); // 2
});

這里有一點(diǎn)我們必須了解:
如果前一個(gè)回調(diào)函數(shù)返回的是Promise對(duì)象,這時(shí)后一個(gè)回調(diào)函數(shù)就會(huì)等待該P(yáng)romise對(duì)象有了運(yùn)行結(jié)果,才會(huì)進(jìn)一步調(diào)用。

(3)Promise.prototype.catch()

Promise.prototype.catch方法是Promise.prototype.then(null, rejection)的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)。這一點(diǎn)官方api中說(shuō)的很清楚。

catch.png

catch 方法可以用于您的promise組合中的錯(cuò)誤處理,這個(gè)方法其實(shí)很簡(jiǎn)單,在這里并不想討論它的使用,而是想討論的是Promise中的錯(cuò)誤的捕抓和處理。
catch返回的Promise狀態(tài)參考上面then的返回值
例如:

var p = new Promise( (resolve, reject) => {
    setTimeout(() => resolve('p'), 10);
});

p.then( ret => {
    console.log(ret);
    throw new Error('then1');
    return 'then1';
}).then( ret => {
    console.log(ret);
    throw new Error('then2');
    return 'then2';
}).catch( err => {
    // 可以捕抓到前面的出現(xiàn)的錯(cuò)誤。
    console.log(err.toString());
});

//執(zhí)行結(jié)果如下:
// p
// Error: then1 

Promise對(duì)象的Error對(duì)象具有傳遞性,會(huì)一直向后傳遞,直到被捕獲為止。也就是說(shuō),錯(cuò)誤總是會(huì)被下一個(gè)catch語(yǔ)句捕獲。
當(dāng)出錯(cuò)時(shí),catch會(huì)先處理之前的錯(cuò)誤,然后通過(guò)return語(yǔ)句,將值繼續(xù)傳遞給后一個(gè)then方法。我們?cè)谏厦胬拥腸atch語(yǔ)句后再添加一個(gè)then語(yǔ)句,看看會(huì)出現(xiàn)什么結(jié)果。

var p = new Promise( (resolve, reject) => {
    setTimeout(() => resolve('p1'), 10);
});

p.then( ret => {
    console.log(ret);
    throw new Error('then1');
    return 'then1';
}).then( ret => {
    console.log(ret);
    throw new Error('then2');
    return 'then2';
}).catch( err => {
    // 可以捕抓到前面的出現(xiàn)的錯(cuò)誤。
    console.log(err.toString());
    return 'err next';
}).then(data=>{
    console.log(data);
});
//執(zhí)行結(jié)果為
//p1
// Error: then1 
//err next 

注意?。。∵@里有個(gè)陷阱等著你往下跳

Promise的錯(cuò)誤處理是一種絕望的設(shè)計(jì)。默認(rèn)情況下,它假定你想讓所有的錯(cuò)誤都被Promise的狀態(tài)吞掉,而且如果你忘記監(jiān)聽(tīng)這個(gè)狀態(tài),錯(cuò)誤就會(huì)默默地凋零/死去。
這時(shí)你可能會(huì)想到把catch語(yǔ)句寫在Promise鏈最后面來(lái)解決,像這樣:

.......

p.then( ret => {
    console.log(ret);
    throw new Error('then1');
    return 'then1';
}).then( ret => {
    console.log(ret);
    throw new Error('then2');
    return 'then2';
}).catch( errors => {
    // 可以捕抓到前面的出現(xiàn)的錯(cuò)誤。
    console.log(errors.toString());
});

要是catch里面函數(shù)本身也有錯(cuò)誤呢?誰(shuí)來(lái)捕獲它?還有一個(gè)沒(méi)人注意的promise:catch(..)返回的promise,我們沒(méi)有對(duì)它進(jìn)行捕獲,也沒(méi)注冊(cè)拒絕處理器。僅僅將另一個(gè)catch(..)貼在鏈條末尾,懸掛著一個(gè)困在未被監(jiān)聽(tīng)的Promise中的,未被捕獲的錯(cuò)誤,即便這種可能性大大減少。

  • 處理未被捕獲的錯(cuò)誤
    Promise應(yīng)當(dāng)增加一個(gè)done(..)方法,它實(shí)質(zhì)上標(biāo)志著Promise鏈的“終結(jié)”。done(..)不會(huì)創(chuàng)建并返回一個(gè)Promise,所以傳遞給done(..)的回調(diào)很明顯地不會(huì)鏈接上一個(gè)不存在的Promise鏈,并向它報(bào)告問(wèn)題。done(..)的拒絕處理器內(nèi)部的任何異常都作為全局的未捕獲錯(cuò)誤拋出(基本上扔到開(kāi)發(fā)者控制臺(tái)),這就和try catch(){ }差不多了。

done()方法我們下一次再介紹,關(guān)于Promise對(duì)象先講到這里,下一次我們繼續(xù)一起學(xué)習(xí)Promise。
謝謝觀看?。?!

最后編輯于
?著作權(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的含義: ??Promise是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和...
    呼呼哥閱讀 2,262評(píng)論 0 16
  • 00、前言Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大。它由社區(qū)...
    夜幕小草閱讀 2,217評(píng)論 0 12
  • Promiese 簡(jiǎn)單說(shuō)就是一個(gè)容器,里面保存著某個(gè)未來(lái)才會(huì)結(jié)束的事件(通常是一個(gè)異步操作)的結(jié)果,語(yǔ)法上說(shuō),Pr...
    雨飛飛雨閱讀 3,484評(píng)論 0 19
  • 9月15日 周四 天氣晴朗 海上生明月,天涯共此時(shí)。 祝大家中秋快樂(lè)。 今年,我確實(shí)跑到海邊,看著明月高懸...
    penny胖妮閱讀 179評(píng)論 1 1
  • 百日計(jì)劃8號(hào)開(kāi)始執(zhí)行,5天回顧, 01:每日書堅(jiān)持在寫,5天,每天最少6個(gè)字的隸書,小朋友簽字確認(rèn)。每天寫簡(jiǎn)書,當(dāng)...
    張瓓閱讀 227評(píng)論 0 0

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