ES6-Promise對象 (上)

前言

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

但是在這之前蚓耽,大家想要使用Promise渠牲,一般會借助于第三方庫,或者當你知道其中的原理以后步悠,也可以手動實現(xiàn)一個簡易的Promise.當然签杈,為了防止不可預知的bug,在生產項目中最好還是不要使用原生的或者自己編寫的Promise(目前為止并不是所有瀏覽器都能很好的兼容ES6)鼎兽,而是使用已經較為成熟的有大量小伙伴使用的第三方Promise庫〈鹄眩現(xiàn)今流行的各大js庫,幾乎都不同程度的實現(xiàn)了Promise谚咬,如jQuery鹦付、Zepto等,只是暴露出來的大都是Deferred對象,當然還有angularJs中的$q择卦。

注:以下所有的測試代碼請在高級瀏覽器或node環(huán)境下運行
我們還是先來看看Promise的真身

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

打開瀏覽器的控制臺我們可以看到:

Promise.png

從控制臺輸出的Promise對象我們可以清楚的看到Promise對象有以下幾種基本方法:
Promise.resolve()
Promise.reject()
Promise.all()
Promise.race()
Promise.prototype.then()
Promise.prototype.catch()
更多點擊Promise官網API敲长,我們先不著急記住他們,只要對Promise對象有個整體的認識秉继。


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

  • pending: 初始狀態(tài), 既不是 fulfilled 也不是 rejected.
  • fulfilled: 成功的操作.
  • rejected: 失敗的操作.
    pending狀態(tài)的Promise對象既可轉換為帶著一個成功值的fulfilled狀態(tài)祈噪,也可變?yōu)閹е粋€失敗信息的 rejected狀態(tài)。當狀態(tài)發(fā)生轉換時尚辑,Promise.then綁定的方法就會被調用辑鲤。(當綁定方法時,如果 Promise對象已經處于fulfilled或rejected狀態(tài)杠茬,那么相應的方法將會被立刻調用月褥,所以在異步操作的完成情況和它的綁定方法之間不存在競爭條件。)
    因為Promise.prototype.then和Promise.prototype.catch方法返回Promises對象, 所以它們可以被鏈式調用澈蝙。
Promise對象活動流程.png

注意吓坚,Promise狀態(tài)的改變只會出現(xiàn)從未完成態(tài)向完成態(tài)或失敗態(tài)轉化,不能逆反灯荧。完成態(tài)和失敗態(tài)不能互相轉化礁击,而且,狀態(tài)一旦轉化逗载,將不能更改哆窿。


2.基本用法

(1)constructor

語法

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

參數(shù)

name desc
executor 帶有resolve、reject兩個參數(shù)的函數(shù)對象厉斟。第一個參數(shù)用在處理執(zhí)行成功的場景挚躯,第二個參數(shù)則用在處理執(zhí)行失敗的場景。一旦我們的操作完成即可調用這些函數(shù)擦秽。

ES6 的 Promise 對象是一個構造函數(shù)码荔,用來生成 Promise 實例漩勤。

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

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

上面代碼中,Promise 構造函數(shù)接受一個函數(shù)作為參數(shù)缩搅,該函數(shù)的兩個參數(shù)分別是 resolve 方法和 reject 方法越败。如果異步操作成功,則用 resolve 方法將 Promise 對象的狀態(tài)硼瓣,從“未完成”變?yōu)椤俺晒Α保磸?pending 變?yōu)?resolved)究飞;如果異步操作失敗,則用 reject 方法將 Promise 對象的狀態(tài)堂鲤,從“未完成”變?yōu)椤笆 保磸?pending 變?yōu)?rejected)亿傅。
Promise 實例生成以后,可以用 then 方法分別指定 resolve 方法和 reject 方法的回調函數(shù)瘟栖。

這里我們要特別注意兩點:
1?? Promise 新建后就會立即執(zhí)行葵擎。
我們來一段代碼測試一下:

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

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

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

2?? 如果調用 resolve 方法和 reject 方法時帶有參數(shù),那么它們的參數(shù)會被傳遞給回調函數(shù)半哟。reject 方法的參數(shù)通常是 Error 對象的實例坪蚁,表示拋出的錯誤;resolve 方法的參數(shù)除了正常的值以外镜沽,還可能是另一個 Promise 實例敏晤,表示異步操作的結果有可能是一個值,也有可能是另一個異步操作缅茉,比如像下面這樣:

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

注意嘴脾,這時p1的狀態(tài)就會傳遞給p2,也就是說蔬墩,p1的狀態(tài)決定了p2的狀態(tài)译打。如果p1的狀態(tài)是pending,那么p2的回調函數(shù)就會等待p1的狀態(tài)改變拇颅;如果p1的狀態(tài)已經是resolved或者rejected奏司,那么p2的回調函數(shù)將會立刻執(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í)行結果如下:


res.png

這里也再次說明第一個注意點提到的Promise 新建后就會立即執(zhí)行樟插;

(2)Promise.prototype.then()

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

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

我們做一個異步加載圖片實際案例來小試牛刀

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"); //來自百度的梅西圖片
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");
});

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


loadPhoto.png
返回值

Promise.prototype.then 方法返回的是一個新的Promise對象,因此可以采用鏈式寫法黄锤,即then方法后面再調用另一個then方法搪缨。

then方法返回的新的Promise對象的行為與then中的回調函數(shù)的返回值有關:

  • 如果then中的回調函數(shù)返回一個值,那么then返回的Promise將會成為接受狀態(tài)鸵熟,并且將返回的值作為接受狀態(tài)的回調函數(shù)的參數(shù)值副编。
  • 如果then中的回調函數(shù)拋出一個錯誤,那么then返回的Promise將會成為拒絕狀態(tài)流强,并且將拋出的錯誤作為拒絕狀態(tài)的回調函數(shù)的參數(shù)值痹届。
  • 如果then中的回調函數(shù)返回一個已經是接受狀態(tài)的Promise呻待,那么then返回的Promise也會成為接受狀態(tài),并且將那個Promise的接受狀態(tài)的回調函數(shù)的參數(shù)值作為該被返回的Promise的接受狀態(tài)回調函數(shù)的參數(shù)值队腐。
  • 如果then中的回調函數(shù)返回一個已經是拒絕狀態(tài)的Promise带污,那么then返回的Promise也會成為拒絕狀態(tài),并且將那個Promise的拒絕狀態(tài)的回調函數(shù)的參數(shù)值作為該被返回的Promise的拒絕狀態(tài)回調函數(shù)的參數(shù)值香到。
  • 如果then中的回調函數(shù)返回一個未定狀態(tài)(pending)的Promise,那么then返回Promise的狀態(tài)也是未定的报破,并且它的終態(tài)與那個Promise的終態(tài)相同悠就;同時,它變?yōu)榻K態(tài)時調用的回調函數(shù)參數(shù)與那個Promise變?yōu)榻K態(tài)時的回調函數(shù)的參數(shù)是相同的充易。

是不是看著有一種迷糊的感覺梗脾,我們這里只看第一種情況。

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

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

這里有一點我們必須了解:
如果前一個回調函數(shù)返回的是Promise對象盹靴,這時后一個回調函數(shù)就會等待該Promise對象有了運行結果炸茧,才會進一步調用。

(3)Promise.prototype.catch()

Promise.prototype.catch方法是Promise.prototype.then(null, rejection)的別名稿静,用于指定發(fā)生錯誤時的回調函數(shù)梭冠。這一點官方api中說的很清楚。

catch.png

catch 方法可以用于您的promise組合中的錯誤處理,這個方法其實很簡單改备,在這里并不想討論它的使用控漠,而是想討論的是Promise中的錯誤的捕抓和處理。
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)的錯誤悬钳。
    console.log(err.toString());
});

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

Promise對象的Error對象具有傳遞性盐捷,會一直向后傳遞,直到被捕獲為止默勾。也就是說碉渡,錯誤總是會被下一個catch語句捕獲。
當出錯時母剥,catch會先處理之前的錯誤滞诺,然后通過return語句,將值繼續(xù)傳遞給后一個then方法环疼。我們在上面例子的catch語句后再添加一個then語句铭段,看看會出現(xiàn)什么結果。

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)的錯誤秦爆。
    console.log(err.toString());
    return 'err next';
}).then(data=>{
    console.log(data);
});
//執(zhí)行結果為
//p1
// Error: then1 
//err next 

注意P蛴蕖!等限!這里有個陷阱等著你往下跳

Promise的錯誤處理是一種絕望的設計爸吮。默認情況下芬膝,它假定你想讓所有的錯誤都被Promise的狀態(tài)吞掉,而且如果你忘記監(jiān)聽這個狀態(tài)形娇,錯誤就會默默地凋零/死去锰霜。
這時你可能會想到把catch語句寫在Promise鏈最后面來解決,像這樣:

.......

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)的錯誤。
    console.log(errors.toString());
});

要是catch里面函數(shù)本身也有錯誤呢桐早?誰來捕獲它癣缅?還有一個沒人注意的promise:catch(..)返回的promise,我們沒有對它進行捕獲哄酝,也沒注冊拒絕處理器友存。僅僅將另一個catch(..)貼在鏈條末尾,懸掛著一個困在未被監(jiān)聽的Promise中的陶衅,未被捕獲的錯誤屡立,即便這種可能性大大減少。

  • 處理未被捕獲的錯誤
    Promise應當增加一個done(..)方法搀军,它實質上標志著Promise鏈的“終結”膨俐。done(..)不會創(chuàng)建并返回一個Promise,所以傳遞給done(..)的回調很明顯地不會鏈接上一個不存在的Promise鏈罩句,并向它報告問題焚刺。done(..)的拒絕處理器內部的任何異常都作為全局的未捕獲錯誤拋出(基本上扔到開發(fā)者控制臺),這就和try catch(){ }差不多了门烂。

done()方法我們下一次再介紹檩坚,關于Promise對象先講到這里,下一次我們繼續(xù)一起學習Promise诅福。
謝謝觀看X椅!氓润!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末赂乐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子咖气,更是在濱河造成了極大的恐慌挨措,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件崩溪,死亡現(xiàn)場離奇詭異浅役,居然都是意外死亡,警方通過查閱死者的電腦和手機伶唯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門觉既,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事瞪讼【” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵符欠,是天一觀的道長嫡霞。 經常有香客問我,道長希柿,這世上最難降的妖魔是什么诊沪? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮曾撤,結果婚禮上端姚,老公的妹妹穿的比我還像新娘。我一直安慰自己盾戴,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布兵多。 她就那樣靜靜地躺著尖啡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪剩膘。 梳的紋絲不亂的頭發(fā)上衅斩,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音怠褐,去河邊找鬼畏梆。 笑死,一個胖子當著我的面吹牛奈懒,可吹牛的內容都是我干的奠涌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼磷杏,長吁一口氣:“原來是場噩夢啊……” “哼溜畅!你這毒婦竟也來了?” 一聲冷哼從身側響起极祸,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤慈格,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后遥金,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浴捆,經...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年稿械,在試婚紗的時候發(fā)現(xiàn)自己被綠了选泻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖滔金,靈堂內的尸體忽然破棺而出色解,到底是詐尸還是另有隱情,我是刑警寧澤餐茵,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布科阎,位于F島的核電站,受9級特大地震影響忿族,放射性物質發(fā)生泄漏锣笨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一道批、第九天 我趴在偏房一處隱蔽的房頂上張望错英。 院中可真熱鬧,春花似錦隆豹、人聲如沸椭岩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽判哥。三九已至,卻和暖如春碉考,著一層夾襖步出監(jiān)牢的瞬間塌计,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工侯谁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锌仅,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓墙贱,卻偏偏與公主長得像热芹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子惨撇,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內容

  • Promise的含義: ??Promise是異步編程的一種解決方案剿吻,比傳統(tǒng)的解決方案——回調函數(shù)和事件——更合理和...
    呼呼哥閱讀 2,170評論 0 16
  • 00丽旅、前言Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調函數(shù)和事件——更合理和更強大纺棺。它由社區(qū)...
    夜幕小草閱讀 2,133評論 0 12
  • Promiese 簡單說就是一個容器榄笙,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果,語法上說祷蝌,Pr...
    雨飛飛雨閱讀 3,358評論 0 19
  • 9月15日 周四 天氣晴朗 海上生明月茅撞,天涯共此時。 祝大家中秋快樂。 今年米丘,我確實跑到海邊剑令,看著明月高懸...
    penny胖妮閱讀 156評論 1 1
  • 百日計劃8號開始執(zhí)行,5天回顧拄查, 01:每日書堅持在寫吁津,5天,每天最少6個字的隸書堕扶,小朋友簽字確認碍脏。每天寫簡書,當...
    張瓓閱讀 209評論 0 0