前言
在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對象我們可以清楚的看到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狀態(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í)行結果如下:
這里也再次說明第一個注意點提到的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ā)生了什么:
返回值
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 方法可以用于您的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椅!氓润!