Promise
是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強大署照。它由社區(qū)最早提出和實現(xiàn)吗浩,ES6
將其寫進了語言標準懂扼,統(tǒng)一了用法,原生提供了Promise對象赶熟。
promise
的特點:
- 對象的狀態(tài)不會受到外界所影響陷嘴,
Promise
對象代表一次異步操作灾挨,只會有三種狀態(tài)Pending
(進行中),Fulfilled
(已成功)地技,Rejected
(失斉彝痢)。只有異步操作的結(jié)果可以決定Promise
對象的狀態(tài)狡相,任何其他操作都無法改變這個狀態(tài)尽棕。 -
Promise
對象的狀態(tài)變化只會有兩種可能:(1)Pending
變成Fulfilled
(2)Pending
變成Rejected
彬伦。只要這兩種情況任意一個發(fā)生单绑,Promise
的狀態(tài)就會固定,不會再發(fā)生變化了歉提。 - 如果狀態(tài)已經(jīng)發(fā)生的改變苔巨,再給給期添加回調(diào)函數(shù)废离,依然可以得到結(jié)果蜻韭。這與
event
不同,事件一旦錯過就沒有了诀豁。 -
缺點: a.
Promise
一旦創(chuàng)建就會立即執(zhí)行,無法中途取消活翩。b. 如果不設置回調(diào)函數(shù),Promise
內(nèi)部發(fā)生的錯誤沮焕,不會反應到外部峦树。c.Pending
狀態(tài)時旦事,無法得知當前進展到哪一狀態(tài)姐浮。
基本用法
- 創(chuàng)建一個
Promise
實例;
var promise = new Promise(function (resolve, reject) {
if(/*成功*/) {
resolve(value);
} else {
reject(error);
}
})
Promise
實例創(chuàng)建完畢,我們需要通過then
方法分別指定Resolved
和Rejected
狀態(tài)的回調(diào)函數(shù)肾扰。
promise.then(function () {
// success code
}, function () {
// failure code
})
then
方法可以接受兩個參數(shù)集晚,第一個參數(shù)對應成功的回調(diào)函數(shù)区匣,第二個對應的是失敗的回調(diào)函數(shù)沉颂,并且第二個參數(shù)的可選的铸屉,不一定需要傳遞。
-
Promise
新建以后就會立即執(zhí)行顷啼。
var promise = new Promise(function (resolve, reject) {
resolve();
console.log('promise')
})
promise.then(function () {
console.log('resolved')
})
console.log('hi');
// 執(zhí)行結(jié)果:
// promise
// hi
// resolved
then
方法指定的回調(diào)函數(shù)钙蒙,將在當前腳本所同步任務執(zhí)行完成才會執(zhí)行间驮,所以resolved
最后輸出竞帽。
Promise.prototype.then()
Promise
實例具有then
方法,也就是then
方法是定義在Promise
的原型對象上的匙奴,他的作用就是添加狀態(tài)改變時的回調(diào)函數(shù)妄荔。
注意:then
方法返回的是一新的Promise
實例(不是原來的那個Promise
實例),因此可以鏈式調(diào)用啦租。
var getJSON = function (url) {
var promise = new Promise(function (resolve, reject) {
var client = new XMLHttpRequest();
client.open('GET', url);
client.onreadystatechange = handler;
client.responseType = 'json';
client.setRequestHeader('Accept', 'application/json');
client.send();
function handler() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
})
return promise;
}
getJSON('/posts.json').then(function (json) {
return json.post;
}).then(function(post){
//post 是上一個回調(diào)函數(shù)傳過來的
})
上面的代碼使用then
方法刷钢,依次指定了兩個回調(diào)函數(shù),第一個回調(diào)函數(shù)完成以后伴澄,會將返回結(jié)果作為參數(shù)非凌,傳入第二個回調(diào)函數(shù)荆针。
getJSON('/post/1.json').then(function(post){
return getJSON('/post/2.json')
}).then(function(post){
console.log('resloved')
}, function(err){
console.log('rejected')
})
上面的代碼航背,第一個then
方法返回了Promise
對象玖媚,這時,第二個then
指定的回調(diào)函數(shù)勺像,要等到這個Promise
對象狀態(tài)改變時才會觸發(fā)吟宦。
Promise.prototype.catch()
Promise.prototype.catch
方法是.then(null, fn)
的別名殃姓,用于指定發(fā)生錯誤的回調(diào)函數(shù)。
getJSON('/post.json').then(function(){
// code
}).catch(function(err){
console.log(err)
})
getJSON
返回一個Promise
對象锋叨,如果是成功狀態(tài)就會走then
里面設置的回調(diào)函數(shù),如果失敗了就會走catch
設置的回調(diào)函數(shù)叫倍,如果then
指定的回調(diào)函數(shù)里面出錯了豺瘤,也會被catch
所捕獲坐求,調(diào)用catch
設置的回調(diào)函數(shù)處理桥嗤。
p.then(function(){
//code
}).catch(function(){
//code
})
// 等同于
p.then(function(){
// code
}).then(null, function(){
//code
})
// 第一種寫法
var promise = new Promise(function(resolve, reject){
try {
throw new Error('test')
} catch (e) {
reject(e)
}
})
promise.catch(function(err){
console.log(err)
})
// 第二種寫法
var promise = new Promise(function(resolve, reject){
reject(new Error('test'))
})
promise.catch(function(error){
console.log(error)
})
上面兩種寫法是等價的,可以看出reject
方法作用荒吏,等同于拋出錯誤绰更。
var promise = new Promise(function(resolve, reject){
resolve('ok');
throw new Error('test')
})
promise.then(function(value){
console.log(value)
}).catch(function(err){
console.log(err)
})
// ok
Promise
在resolve
語句后面再拋出錯誤儡湾,是不會被catch
捕獲的盒粮,等于沒有拋出奠滑。
getJSON('/post/1.json').then(function(post){
return getJSON('/post/2.json')
}).then(function(json2){
// code
}).catch(function(e){
console.log(e)
// 捕獲前面三個promise對象的錯誤
})
Promise
對象的錯誤具有“冒泡”的性質(zhì)宋税,會向后面一直傳遞杰赛,直到被捕獲為止。
//不推薦的寫法
promise.then(function(data){
//success
}, function(err){
// failure
})
// 推薦寫法
promise.then(function(data){
//success
}).catch(function(err){
console.log(err)
})
第二種方法優(yōu)于第一種寫法根时,理由是第二種可以捕獲前面then
里面的錯誤蛤迎,語法頁更貼近(try/catch
)替裆。
var someAsyncThing = function() {
return new Promise(function() {
resolve(x + 2)
})
}
someAsyncThing().then(function() {
console.log('..')
})
上面的代碼中,someAsynicThing
函數(shù)產(chǎn)生的Promise
對象會報錯宜咒,但是沒有指定catch
方法這個錯誤是不會被捕獲的故黑,也不會被傳遞到外層的场晶。
var promise = new Promise(function(resolve, reject) {
resolve('ok');
setTimeout(function(){
throw new Error('test')
}, 0)
})
promise.then(function(value) {
console.log(value);
}).catch(function(err) {
console.log(err);
})
// ok
// VM672:4 Uncaught Error: test
Promise
指定在下輪的事件循環(huán)
里面在拋出一個錯誤峰搪,到那個時候Promise
的運行已經(jīng)結(jié)束凯旭,所以這個錯誤會在函數(shù)體外拋出罐呼,冒泡到最外層成為未捕獲的錯誤嫉柴。
var someAsyncthing = function() {
return new Promise(function(resolve, reject){
// 將報錯,x沒有申明
resolve(x + 2)
})
}
someAsyncthing().catch(function(err){
console.log('err:', err)
return '123'
}).then(function(str){
console.log('ok', str)
})
// err: ReferenceError: x is not defined
// at <anonymous>:4:13
// at Promise (<anonymous>)
// at someAsyncthing (<anonymous>:2:10)
// at <anonymous>:7:1
//ok 123
catch
方法返回的還是一個Promise
對象,因此后面還是可以接著調(diào)用then
方法匙握。
var someAsyncthing = function() {
return new Promise(function(resolve, reject){
resolve(2)
})
}
someAsyncthing().catch(function(err){
console.log('err:', err)
return '123'
}).then(function(str){
console.log('ok', str)
// x 未定義會報錯
return x;
})
//ok 2
//ReferenceError: x is not defined
// at <anonymous>:12:3
// at <anonymous>
上面代碼圈纺,Promise
里面沒有錯誤,就會跳過catch
的方法灯谣,直接執(zhí)行下面的then
方法蛔琅,then
里面在出錯就與前面的catch
無關了揍愁,錯誤也不會被捕獲莽囤。
var someAsyncthing = function() {
return new Promise(function(resolve, reject){
resolve(2)
})
}
someAsyncthing().catch(function(err){
console.log('err:', err)
// x 未定義
return x
}).then(function(str){
console.log('ok', str)
})
// ok 2
catch
里面出了錯誤朽缎,因為后面沒有catch
了话肖,導致這個錯誤不會被捕獲葡幸,也不會被傳遞到最外層蔚叨。
總結(jié): 為了盡可能多的捕獲到錯誤床蜘,應該將catch
寫在所有then
的最后面,這樣不僅可以捕獲到promise
的錯誤蔑水,也能捕獲到前面then
里面錯誤.
Promise.all()
Promise.all
方法用于將多個Promise
實例邢锯,包裝成一個新的Promise
實例。
var p = Promise.all([p1, p2, p3])
Promise.all
接受一個數(shù)組作為參數(shù)搀别,p1
丹擎,p2
,p3
都是Promise
實例歇父,如果不是,就會先調(diào)用Promise.resolve
方法榜苫,將其轉(zhuǎn)為Promise
實例护戳。(Promise.all
的參數(shù)可以不一定是數(shù)組,但一定是要實現(xiàn)Iterator接
口的单刁,且返回的每個成員都是Promise實例)
p
的狀態(tài)是由p1
,p2
,p3
決定的:
- 只有
p1
,p2
,p3
的狀態(tài)都變成fulfilled
灸异,p
的狀態(tài)就變成fulfilled
府适,此時p1
,p2
,p3
的返回組成一個數(shù)組,傳遞給p的回調(diào)函數(shù)肺樟。 - 只有
p1
,p2
,p3
中有一個的狀態(tài)都變成rejected
檐春,p
的狀態(tài)就變成rejected
,此時第一個變成rejected
的實例的返回值么伯,傳遞給p
的回調(diào)函數(shù)疟暖。
var a = function(){
var t = ~~(Math.random()*1000)
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log(t)
resolve(t)
},t)
})
}
var p1 = a(); var p2 = a();
Promise.all([p1,p2]).then(([p1,p2])=>{console.log(123, p1,p2)})
// 562
// 879
// 123 879 562
上面p1
和p2
是兩個異步的操作,只有等到兩個結(jié)果都返回了才會觸發(fā)Promise.all
的then
的方法
// 由于是隨機數(shù)要多試幾次
var a = function(){
var t = ~~(Math.random()*1000)
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log(t)
if(t < 500){
reject(new Error('<500'))
}
resolve(t)
},t)
})
}
// 如果出錯田柔,p1將不是a函數(shù)返回的Promise俐巴,而是catch返回的promise
var p1 = a().catch((err) => {console.log('err:', err)});
var p2 = a().catch((err) => {console.log('err:', err)});
Promise.all([p1,p2]).then(([p1,p2])=>{console.log(123, p1,p2)}).catch((err) => {console.log('all',err)})
//7 a 里面的輸出
//err: Error: <500 p1 的catch的輸出
// at <anonymous>:7:12
// 687 a 里面的輸出
//123 undefined 687 Promise.all的then輸出
上面p1
和p2
都有可能會Rejected
,一旦Rejected
就會被他們的catch
所捕獲硬爆,然后就會返回一個全新的Promise
對象不再是原來的那個欣舵,全新的實例執(zhí)行完catch
以后也會變成Resolved
, 導致Promise.all
方法里面的兩個實例都是Resolved
缀磕,則會走Promise.all
的then
方法缘圈,而不會是catch
方法。
Promise.race()
Promise.race
方法同樣是將多個Promise
的實例袜蚕,包裝成一個新的Promise
實例糟把。
var p = Promise.race([p1,p2,p3])
上面代碼,只要p1
,p2
,p3
之中有一個實例率先改變了狀態(tài)牲剃,p
的狀態(tài)就會跟著改變遣疯,那個率先改變的Promise
實例的返回值,就傳遞給p
的回調(diào)函數(shù)凿傅。
他處理參數(shù)的方法的原則和Promise.all
是一樣的缠犀。
var a = function(){
var t = ~~(Math.random()*1000)
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log(t)
resolve(t)
},t)
})
}
var p1 = a()
var p2 = a()
Promise.race([p1,p2]).then(t =>{console.log(123, t)})
// 87 a 函數(shù)輸出
// 123 87 Promise.race的then輸出
// 591 a 函數(shù)輸出
Promise.resolve()
有時需要將現(xiàn)有的對象轉(zhuǎn)化成Promise
對象,Promise.resolve
方法就起到這個作用狭归。
var jqPromise = Promise.resolve($.ajax('/1.json'))
上面的代碼將jQuery
生成的deferred
對象夭坪,轉(zhuǎn)成一個新的Promise
對象。
Promise.resolve('foo')
// 等價于
new Promise(function(resolve, reject){
resolve('foo')
})
Promise.resolve方法的參數(shù)分為四種情況
a. 參數(shù)是一個Promise
實例
如果參數(shù)是一個Promise
實例过椎,那么Promise.resolve
將不做任何修改室梅,原封不動的返回這個實例。
b. 參數(shù)是一個thenable
對象
thenable
對象是指具有then方法的對象疚宇,例如:
// thenable 對象
let thenable = {
then: function(resolve, reject){
resolve(42)
}
}
let p1 = Promise.resolve(thenable);
p1.then(function(value){
console.log(value) // 42
})
thenable
對象的then
方法執(zhí)行以后亡鼠,對象p1
的狀態(tài)就會變成resolved
,從而立即執(zhí)行最后的那個then
方法指定的函數(shù)敷待,輸出42
c. 參數(shù)不是具有then
方法的對象间涵,或根本就不是對象
如果參數(shù)是一個原始值,或者是一個不具有then
方法的對象榜揖,則Promise.resolve
方法返回一個新的Promise
對象勾哩,狀態(tài)為Resolved
.
var p = Promise.resolve('hello')
p.then(function(s){
console.log(s)
})
// hello
上面的代碼生成一個新的Promise
對象的實例p
抗蠢,由于字符串hello
不屬于異步操作(判斷方法字符串不具有then
方法),返回的Promise
實例的狀態(tài)從一生成就是Resolved
思劳,所以回調(diào)函數(shù)立即執(zhí)行迅矛。Promise.resolve
方法的參數(shù),會同時傳回給回調(diào)函數(shù)潜叛。
d. 不帶任何參數(shù)
Promise.resolve
方法允許調(diào)用時不帶任何參數(shù)秽褒,直接返回一個Resolved
狀態(tài)的Promise
對象。
注意: 立即resolve
的Promise
對象威兜,實在本輪事件循環(huán)
的結(jié)束時销斟,而不是在下一輪事件循環(huán)
的開始時
setTimeout(function(){
console.log('setTimeout')
}, 0)
Promise.resolve().then(function(){
console.log('Promise.resolve')
})
console.log('console.log()')
// console.log() 順序執(zhí)行
// Promise.resolve 本輪事件循環(huán)的尾部
// setTimeout 下輪事件循環(huán)的開始
setTimeout(fn, 0)
實在下一輪事件循環(huán)的開始的時候執(zhí)行的,Promise.resolve()
實在本輪事件循環(huán)的結(jié)束時執(zhí)行椒舵,console.log()
立即執(zhí)行蚂踊,所以最先輸出。
Promise.reject()
跟Promise.resolve
方法類似逮栅,生成相對的狀態(tài)為Rejected
的Promise
實例悴势。
var p = Promise.reject('出錯了');
// 等同于
var p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (s) {
console.log(s)
});
// 出錯了
生成的p
的狀態(tài)為Rejected
的窗宇,回調(diào)函數(shù)立馬執(zhí)行措伐。
注意: Promise.reject()
方法的參數(shù),會原封不動的作為reject
的理由军俊,比那成后續(xù)方法的參數(shù)侥加,這一點與Promise.resolve
方法不一致
const thenable = {
then(resolve, reject) {
reject('出錯了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
兩個有用的附加方法
ES6的Promise API提供的方法不是很多,有些有用的方法可以自己部署粪躬。下面介紹如何部署兩個不在ES6之中担败、但很有用的方法。
Done()
Promise
對象的回調(diào)鏈媒鼓,無論是以then
方法或者catch
方法結(jié)尾鸭巴,要是最后一個方法拋出錯誤牧挣,都有可能無法捕捉到(因為Promise
內(nèi)部的錯誤不會冒泡到全局)。因此我們可以提供一個done
方法狈网,總是處于回調(diào)鏈的尾端,保證拋出任何可能出現(xiàn)的錯誤笨腥。
asyncFunc()
.then()
.then()
.catch()
.done()
他的實現(xiàn)頁很簡單
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function (reason) {
// 拋出全局錯誤
setTimeout(() => {throw reason}, 0)
})
}
done
方法的使用拓哺,可以像then
方法那樣用,提供Fulfilled
和Rejected
狀態(tài)的回調(diào)函數(shù)脖母,也可以不提供任何參數(shù)士鸥。但不管怎樣,done
都會捕捉到任何可能出現(xiàn)的錯誤谆级,并向全局拋出烤礁。
finally()
finally
方法不管狀態(tài)是成功還是失敗讼积,都會執(zhí)行的的操作,他與done
的最大的區(qū)別脚仔,是他接受一個回調(diào)函數(shù)作為參數(shù)币砂,該函數(shù)不管怎樣都必須執(zhí)行。
server.listen(0)
.then(function () {
// run test
})
.finally(server.stop);
實現(xiàn)
Promise.prototype.finally = function (callback) {
let P = this.cunstructor;
return this.then(function(value){
P.resolve(callback()).then(function(){
return value;
})
}, function(reason){
P.reject(callback()).then(function(){
throw reason;
})
})
}
上面代碼中玻侥,不管前面的Promise
是fulfilled
還是rejected
决摧,都會執(zhí)行回調(diào)函數(shù)callback
。