1.Promise是什么孝偎?
Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強大岔帽。它由社區(qū)最早提出和實現(xiàn)昵仅,ES6 將其寫進了語言標準,統(tǒng)一了用法梢卸,原生提供了Promise對象走诞。
2. 基本用法
Promise對象是一個構造函數(shù),用來生成Promise實例蛤高。下面我們來
1.創(chuàng)建一個promise實例:
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
- Promise構造函數(shù)接受一個函數(shù)作為參數(shù)蚣旱,該函數(shù)的兩個參數(shù)分別是resolve和reject。它們是兩個函數(shù)戴陡,由 JavaScript 引擎提供塞绿,不用自己部署。
- resolve函數(shù)的作用是恤批,將Promise對象的狀態(tài)從“未完成”變?yōu)椤俺晒Α保磸?pending 變?yōu)?fulfilled)异吻,在異步操作成功時調(diào)用,并將異步操作的結果喜庞,作為參數(shù)傳遞出去诀浪;
- reject函數(shù)的作用是,將Promise對象的狀態(tài)從“未完成”變?yōu)椤笆 保磸?pending 變?yōu)?rejected)赋荆,在異步操作失敗時調(diào)用笋妥,并將異步操作報出的錯誤,作為參數(shù)傳遞出去窄潭。
2.使用promise實例
Promise實例生成以后春宣,可以用then方法
分別指定fulfilled狀態(tài)和rejected狀態(tài)的回調(diào)函數(shù),從而接收傳遞過來的狀態(tài)嫉你。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
- then方法可以接受兩個回調(diào)函數(shù)作為參數(shù)月帝。
- 第一個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)閒ulfilled時調(diào)用
- 第二個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)閞ejected時調(diào)用。其中幽污,第二個函數(shù)是可選的嚷辅,不一定要提供
- 這兩個函數(shù)都接受Promise對象傳出的值作為參數(shù)。
3. 特點
從上面的用法看距误,我們可以了解到Promise有如下特點:
- Promise對象代表一個異步操作簸搞,有三種狀態(tài):pending(進行中)扁位、fulfilled(已成功)和rejected(已失敗)趁俊。只有調(diào)用了resolve/reject函數(shù)來處理結果域仇,才可以決定當前是哪一種狀態(tài),任何其他操作都無法改變這個狀態(tài)寺擂。這也是Promise這個名字的由來暇务,它的英語意思就是“承諾”,表示其他手段無法改變怔软。
舉個例子:
var get = new Promise(function (resolve, reject) {
console.log(1);
resolve(2)
console.log(3)
setTimeout(function(){
console.log(5)
},1);
})
get.then(function (value) {
console.log(value);
})
console.log(4);
//輸出結果為:1 3 4 2 5
從上面例子我們可以看出垦细,只要使用new Promise,相當于創(chuàng)建一個異步操作挡逼。只有調(diào)用resolve或者reject才能觸發(fā)異步操作括改。所以我們一般在promise內(nèi)放置異步操作(如請求等),當返回結果挚瘟,調(diào)用resolve或者reject叹谁。這里為了簡潔明了說明其是異步,所以沒有放異步操作乘盖,直接調(diào)用了resolve函數(shù)焰檩,從打印結果就可以看出其是異步操作。
- Promise對象的狀態(tài)改變订框,只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected析苫。當處于pending狀態(tài)時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)穿扳。
我們更改上面例子:
var get = new Promise(function (resolve, reject) {
console.log(1);
resolve(2)
reject(5)
console.log(3)
})
get.then(function (value) {
console.log(value);
},function(error){
console.log(error);
})
console.log(4);
//輸出結果仍為:1 3 4 2
從例子結果可以看出衩侥,promise對象要么是fulfilled,要么是rejected矛物,所以不能同時輸出
-
一般來說茫死,調(diào)用resolve或reject以后,Promise 的使命就完成了履羞,后繼操作應該放到then方法里面峦萎,而不應該直接寫在resolve或reject的后面。所以忆首,最好在它們前面加上return語句爱榔,這樣就不會有意外。
new Promise((resolve, reject) => {
return resolve(1);
// 后面的語句不會執(zhí)行
console.log(2);
})
下面是一個用Promise對象實現(xiàn)的 Ajax 操作的例子糙及。
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出錯了', error);
});
4. Promise.prototype.then()
Promise 實例具有then方法详幽,也就是說,then方法是定義在原型對象Promise.prototype上的。它的作用是為 Promise 實例添加狀態(tài)改變時的回調(diào)函數(shù)唇聘。前面說過版姑,then方法的第一個參數(shù)是resolved狀態(tài)(已定型,指的是fulfilled狀態(tài)))的回調(diào)函數(shù)雳灾,第二個參數(shù)(可選)是rejected狀態(tài)的回調(diào)函數(shù)漠酿。
上面我們講過 回調(diào)地域問題,現(xiàn)在我們用promise進行鏈式調(diào)用來解決該問題
const request = url => {
return new Promise((resolve, reject) => {
$.get(url, data => {
resolve(data)
});
})
};
request(url).then(data1 => {
return request(data1.url);
}).then(data2 => {
return request(data2.url);
}).then(data3 => {
console.log(data3);
})
從代碼上看谎亩,是不是更直觀更簡潔。
5. Promise.prototype.catch
Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的別名宇姚,用于指定發(fā)生錯誤時的回調(diào)函數(shù)匈庭。我們分別講解then函數(shù)和catch函數(shù),對比兩者的區(qū)別:
5.1 then函數(shù)的第二個參數(shù):錯誤回調(diào)rejection
1.當調(diào)用reject函數(shù)時候浑劳,執(zhí)行rejection回調(diào)
var get = new Promise(function (resolve, reject) {
reject(a)
})
get.then(null, function (error) {
console.log(error); //1
})
運行結果如下:
2.當程序運行錯誤阱持,執(zhí)行rejection回調(diào)
var get = new Promise(function (resolve, reject) {
console.log(a);
})
get.then(null, function (error) {
console.log(error);
})
運行結果和上面一樣:
由上面2個例子我們可以知道,reject等同于拋出錯誤魔熏。供后面回調(diào)函數(shù)捕獲錯誤衷咽。
2.Promise 內(nèi)部的錯誤不會影響到 Promise 外部的代碼運行,通俗的說法就是“Promise 會吃掉錯誤”(js不會因為錯誤崩潰導致下面代碼無法運行)蒜绽。
var get = new Promise(function (resolve, reject) {
reject(a)
})
console.log(1);
setTimeout(function(){
console.log(2)
},1);
運行結果如下:
從結果可以看出镶骗,我們并沒有使用回調(diào)函數(shù)來捕獲錯誤,所以瀏覽器會報錯躲雅,但是并沒有終止腳本運行鼎姊。
3.Promise 的狀態(tài)一旦改變,就永久保持該狀態(tài)相赁,不會再變了相寇。所以在resolve語句后面,再拋出錯誤钮科,不會被捕獲
var get = new Promise(function (resolve, reject) {
resolve('ok')
console.log(a)
console.log(3)
})
get.then(null,function(error){
console.log(error); //沒有執(zhí)行
})
console.log(1);
setTimeout(function(){
console.log(2)
},1);
// 1 2
從上面例子可以看出唤衫,在resolve('ok')后面調(diào)用未定義變量a拋出錯誤,回調(diào)函數(shù)并沒有執(zhí)行捕獲绵脯。在這有個奇怪的問題佳励,上面我們在講解promise特點時候,舉過例子證明resolve后面語句也會執(zhí)行桨嫁,這里為什么打印結果不是 3 1 2
?
這里需要注意的是:由于resolve后面拋出錯誤植兰,所以后面語句會被阻止運行(同步異步都會被阻止)。如果沒有拋出錯誤璃吧,則正常運行楣导。
5.2 catch函數(shù)
上面講了then函數(shù)的第二個參數(shù):我們知道了如果在promise中如果有錯,可以使用錯誤回調(diào)rejection進行捕獲畜挨。那catch呢筒繁?和rejection一樣噩凹,也一樣可以捕獲。
var get = new Promise(function (resolve, reject) {
reject(a)
})
get.catch(function (error) {
console.log(error); //1
})
運行結果如下:
那么他們兩個到底有什么區(qū)別呢毡咏?唯一的區(qū)別就是驮宴,如果在 then 的第一個成功回調(diào)函數(shù)里拋出了異常,catch 能捕獲到呕缭,而錯誤回調(diào)函數(shù)捕獲不到
var get = new Promise(function (resolve, reject) {
resolve('ok')
})
get.then(function (value) {
console.log(a);
console.log(value); //因報錯被阻止運行
},function(error){
console.log(error); //沒有執(zhí)行捕獲堵泽,所以瀏覽器報錯
})
所以運行結果如下:
現(xiàn)在我們改成catch就可以捕獲
var get = new Promise(function (resolve, reject) {
resolve('ok')
})
get.then(function (value) {
console.log(a);
console.log(value); //因報錯被阻止運行
}).catch(function(error){
console.log(error); //可以捕獲
})
運行結果如下:
所以一般總是建議,不用rejection回調(diào)函數(shù)來捕獲錯誤恢总,用catch方法迎罗,這樣既可以處理 Promise 內(nèi)部發(fā)生的錯誤,又可以處理成功回調(diào)函數(shù)中的錯誤片仿。
有的人可能會好奇纹安,那假若都有錯誤怎么辦?
var get = new Promise(function (resolve, reject) {
console.log(e);
resolve('ok') //報錯所以直接進入捕獲函數(shù)砂豌,因為只可能有一種狀態(tài)
console.log(1); //因報錯被阻止運行
})
get.then(function (value) {
console.log(value); //不會調(diào)用
}).catch(function(error){
console.log(error);
})
運行結果如下:
Promise對象的狀態(tài)改變厢岂,只可能有一種結果,要么成功阳距,要么失敗塔粒,不能改變,因為上面直接報錯娄涩,所以直接進入catch函數(shù)中窗怒。這里只以catch舉例,實際此時用catch或者rejection回調(diào)函數(shù)都可以蓄拣。
注意:如果同時用rejection回調(diào)函數(shù)和catch函數(shù)捕獲錯誤扬虚,只會優(yōu)先執(zhí)行rejection回調(diào)函數(shù),不會執(zhí)行catch函數(shù)球恤。
6. Promise.prototype.finally
finally方法用于指定不管 Promise 對象最后狀態(tài)如何辜昵,都會執(zhí)行的操作。該方法是 ES2018 引入標準的
var get = new Promise(function (resolve, reject) {
resolve(1) //報錯所以直接進入捕獲函數(shù)咽斧,因為只可能有一種狀態(tài)
})
get.then(function (value) {
console.log(value); //不會調(diào)用
}).catch(function(error){
console.log(error);
}).finally(function(){
console.log(2);
})
console.log(3);
setTimeout(() => {
console.log(4);
}, 0);
// 3 1 2 4
這里只舉了成功狀態(tài)堪置,失敗狀態(tài)一樣,就不舉例了张惹。
7. Promise.resolve(value)
該方法的作用是:把value轉換成Promise對象舀锨。這里說的Promise對象指的是由Promise構造函數(shù)生成的實例。
如果傳入不同類型的 value 值宛逗,返回結果也有區(qū)別:
- value本身就是Promise對象坎匿,返回結果和入?yún)alue相同
let get = new Promise(function (resolve, reject) {
resolve(1)
})
let p1= Promise.resolve(get)
console.log(p1===get); //true
- value是個thenable對象(即該對象有屬性名字為then方法),返回結果為Promise對象,其跟隨 thenable 對象中的then函數(shù)狀態(tài)(resolved/rejected)
//demo1
let thenable = {
then: function (resolve, reject) {
resolve(1);
}
};
let p1 = Promise.resolve(thenable)
p1.then(function(value){
console.log(value); //1
})
//demo2
let thenable = {
then: function (resolve, reject) {
reject(2);
}
};
let p1 = Promise.resolve(thenable)
p1.then(null,function(error){
console.log(error) //2
})
我們平常用的jquery的ajax就是thenable對象,我們打印看下:
console.log($.ajax());
打印結果如下圖:
- value不是具有then方法的對象替蔬,或根本就不是對象如字符串告私、數(shù)值等,返回結果為一個resolved狀態(tài)的 Promise 對象
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
console.log(p)
打印結果如下圖:
所以承桥,假如我們用錯誤回調(diào)函數(shù)接收驻粟,是不會執(zhí)行的。
const p = Promise.resolve('Hello');
p.then(null,function (error){
console.log(error)
});
console.log(p);
打印結果如下圖:
- value不傳凶异,返回結果為一個resolved狀態(tài)的 Promise 對象
const p = Promise.resolve();
p.then(function (s){
console.log(s)
});
console.log(p);
打印結果如下圖:
8. Promise.reject(value)
該方法的作用是:把value轉換成Promise對象蜀撑。這里說的Promise對象指的是由Promise構造函數(shù)生成的實例。
如果傳入不同類型的 value 值唠帝,返回結果都為一個rejected狀態(tài)的 Promise實例(和上面講的resolve方法不一樣)屯掖。所以調(diào)用該實例會直接調(diào)用catch方法或者then第二個參數(shù),其值就是傳進去的value值襟衰。
- value是promise對象
let get = new Promise(function (resolve, reject) {
resolve(1)
})
let p1= Promise.reject(get)
p1.catch(function(value){
console.log(value===get); //true
})
注意,如果value為promise對象粪摘,該對象里面不能用reject或者語法錯誤瀑晒,具體原因暫不清楚。
let get = new Promise(function (resolve, reject) {
reject(1)
})
let p1= Promise.reject(get)
p1.catch(function(value){
console.log(value===get);
})
- value是個thenable對象
let thenable = {
then: function (resolve, reject) {
resolve(1);
}
};
let p1 = Promise.resolve(thenable)
p1.catch(function(value){
console.log(value===thenable); //true
})
- value不是具有then方法的對象徘意,或根本就不是對象如字符串苔悦、數(shù)值等
const p = Promise.reject('Hello');
p.catch(function (s){
console.log(s)
}); //Hello
- value不傳
let p1 = Promise.reject()
p1.catch(function(value){
console.log(value);
})
console.log(1)
//1 undefined
注意,如果不傳并且不catch捕獲椎咧,會報錯玖详。比如下面代碼:
let p1 = Promise.reject()
console.log(1)
運行結果如下:
9. Promise.all(arr)
Promise.all方法用于將多個 Promise 實例,包裝成一個新的 Promise 實例勤讽。參數(shù)為數(shù)組蟋座。用法如下:
const p = Promise.all([p1, p2, p3]);
上面代碼中,Promise.all方法接受一個數(shù)組作為參數(shù)脚牍,p1向臀、p2、p3都是 Promise 實例诸狭,如果不是券膀,就會先調(diào)用之前講到的Promise.resolve方法,將參數(shù)轉為 Promise 實例驯遇,再進一步處理芹彬。
p的狀態(tài)由p1、p2叉庐、p3決定舒帮,分成兩種情況。
- 只有p1、p2会前、p3的狀態(tài)都變成fulfilled好乐,p的狀態(tài)才會變成fulfilled。此時p1瓦宜、p2蔚万、p3的返回值組成一個數(shù)組,傳遞給p的then回調(diào)函數(shù)临庇。
- 只要p1反璃、p2、p3之中有一個被rejected假夺,p的狀態(tài)就變成rejected淮蜈,此時第一個被reject的實例的返回值,會傳遞給p的錯誤回調(diào)函數(shù)已卷。
10. Promise.race(arr)
Promise.race方法同樣是將多個 Promise 實例梧田,包裝成一個新的 Promise 實例。參數(shù)為數(shù)組侧蘸。用法如下:
const p = Promise.race([p1, p2, p3]);
上面代碼中裁眯,Promise.all方法接受一個數(shù)組作為參數(shù),p1讳癌、p2穿稳、p3都是 Promise 實例,如果不是晌坤,就會先調(diào)用之前講到的Promise.resolve方法逢艘,將參數(shù)轉為 Promise 實例,再進一步處理骤菠。
和all方法不同的是:
- 只要p1它改、p2、p3之中有一個實例率先改變狀態(tài)娩怎,p的狀態(tài)就跟著改變搔课。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調(diào)函數(shù)截亦。
參考來源: