1. 基本用法
**例 1.1 創(chuàng)造一個Promise實例 **
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
例 1.2 用then方法分別指定Resolved狀態(tài)和Reject狀態(tài)的回調(diào)函數(shù)
promise.then(function(value) {
// success
}, function(error) {
// failure
});
then
的第二個參數(shù)一般不寫,最后統(tǒng)一用catch
捕捉異常
例 1.3 Promise新建后就會立即執(zhí)行
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('Resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// Resolved
Promise新建后立即執(zhí)行浙宜,所以首先輸出的是“Promise”。然后,then
方法指定的回調(diào)函數(shù),將在當(dāng)前腳本所有同步任務(wù)執(zhí)行完才會執(zhí)行影兽,所以“Resolved”最后輸出鲫忍。
例 1.4 resolve()和reject()帶有參數(shù)
若resolve(xxx)
和reject(new Error())
,則參數(shù)會被傳遞給回調(diào)函數(shù)。reject
參數(shù)通常是new Error()
表示拋出的錯誤; resolve
函數(shù)的參數(shù)除了正常的值以外闸衫,還可能是另一個Promise實例 ,表示異步操作的結(jié)果有可能是一個值诽嘉,也有可能是另一個異步操作.
var p1 = new Promise((resolve, reject) => {
resolve('111')
// reject(new Error('222'))
})
p1
.then(data => console.log(data)) // '111'
.catch(err => console.log(err)) // 捕獲reject錯誤
var p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
var p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
p1
是一個Promise蔚出,3秒之后變?yōu)?code>rejected。p2
的狀態(tài)在1秒之后改變虫腋,resolve
方法返回的是p1
骄酗。由于p2
返回的是另一個 Promise,導(dǎo)致p2
自己的狀態(tài)無效了悦冀,由p1
的狀態(tài)決定p2
的狀態(tài)趋翻。所以,后面的then
語句都變成針對后者(p1
)盒蟆。又過了2秒踏烙,p1
變?yōu)?code>rejected,導(dǎo)致觸發(fā)catch
方法指定的回調(diào)函數(shù)茁影。
例 1.5 Promise.prototype.then()
then
方法的第一個參數(shù)是Resolved狀態(tài)的回調(diào)函數(shù)宙帝,第二個參數(shù)(可選)是Rejected狀態(tài)的回調(diào)函數(shù)。
采用鏈?zhǔn)降?code>then募闲,可以指定一組按照次序調(diào)用的回調(diào)函數(shù)步脓。這時,前一個回調(diào)函數(shù)浩螺,有可能返回的還是一個Promise對象(即有異步操作)靴患,這時后一個回調(diào)函數(shù),就會等待該P(yáng)romise對象的狀態(tài)發(fā)生變化要出,才會被調(diào)用鸳君。
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("Resolved: ", comments),
err => console.log("Rejected: ", err)
);
例 1.6 Promise.prototype.catch()
Promise.prototype.catch
方法是.then(null, rejection)
的別名,用于指定發(fā)生錯誤時的回調(diào)函數(shù)患蹂。
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
var promise = new Promise(function(resolve, reject) {
// resolve('ok')
// 寫法 1
reject(new Error('test'));
// 寫法 2
throw new Error('test');
// 寫法 3
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
注意:若以上代碼resolve('ok')
生效或颊,即Promise 在resolve
語句后面砸紊,再拋出錯誤,不會被捕獲囱挑,等于沒有拋出醉顽。因為 Promise 的狀態(tài)一旦改變,就永久保持該狀態(tài)平挑,不會再變了游添。
var promise = new Promise(function(resolve, reject) {
resolve('ok');
setTimeout(function() { throw new Error('test') }, 0)
});
promise.then(function(value) { console.log(value) });
// ok
// Uncaught Error: test
上面代碼中,Promise 指定在下一輪“事件循環(huán)”再拋出錯誤通熄,結(jié)果由于沒有指定使用try...catch
語句唆涝,就冒泡到最外層,成了未捕獲的錯誤唇辨。因為此時廊酣,Promise的函數(shù)體已經(jīng)運(yùn)行結(jié)束了,所以這個錯誤是在Promise函數(shù)體外拋出的赏枚。
Node 有一個unhandledRejection
事件啰扛,專門監(jiān)聽未捕獲的reject
錯誤.
process.on('unhandledRejection', function (err, p) {
console.error(err.stack)
});
跟傳統(tǒng)的try/catch代碼塊不同的是,如果沒有使用catch方法指定錯誤處理的回調(diào)函數(shù)嗡贺,Promise對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應(yīng)鞍帝。
var someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行會報錯诫睬,因為x沒有聲明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
上面代碼中,someAsyncThing
函數(shù)產(chǎn)生的Promise對象會報錯帕涌,但是由于沒有指定catch
方法摄凡,這個錯誤不會被捕獲,也不會傳遞到外層代碼蚓曼,導(dǎo)致運(yùn)行后沒有任何輸出亲澡。注意,Chrome瀏覽器不遵守這條規(guī)定纫版,它會拋出錯誤“ReferenceError: x is not defined”床绪。
因為Promise內(nèi)部的錯誤不會冒泡到全局,不管以then
方法或catch
方法結(jié)尾其弊,要是最后一個方法拋出錯誤癞己,都有可能無法捕捉到,此時你需要done()
--例1.11
例 1.7 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
方法,將參數(shù)轉(zhuǎn)為Promise實例 愉耙,再進(jìn)一步處理贮尉。(Promise.all
方法的參數(shù)可以不是數(shù)組,但必須具有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之中有一個被rejected搔谴,p的狀態(tài)就變成rejected魁袜,此時第一個被reject的實例 的返回值,會傳遞給p的回調(diào)函數(shù)敦第。
用 forEach()
處理promise
即在使用for
循環(huán)峰弹、foreach()
的時候如何使用promise
// 錯誤示范
// I want to remove() all docs
db.allDocs({include_docs: true}).then(function (result) {
result.rows.forEach(function (row) {
db.remove(row.doc);
});
}).then(function () {
// I naively believe all docs have been removed() now!
});
這段代碼的問題在于第一個回調(diào)函數(shù)實際上返回的是 undefined
,也就意味著第二個函數(shù)并不是在所有的db.remove()
執(zhí)行結(jié)束之后才執(zhí)行芜果。事實上鞠呈,第二個函數(shù)的執(zhí)行不會有任何延時,它執(zhí)行的時候被刪除的doc數(shù)量可能為任意整數(shù)右钾。
這時候你需要的是 Promise.all()
// 正確的寫法
db.allDocs({include_docs: true}).then(function (result) {
return Promise.all(result.rows.map(function (row) {
return db.remove(row.doc);
}));
}).then(function (arrayObject) {
// All docs have really been removed() now!
})
例 1.8 Promise.race()
var p = Promise.race([p1, p2, p3]);
只要p1
蚁吝、p2
、p3
之中有一個實例 率先改變狀態(tài)霹粥,p
的狀態(tài)就跟著改變灭将。那個率先改變的 Promise 實例 的返回值,就傳遞給p
的回調(diào)函數(shù)后控。
如果指定時間內(nèi)沒有獲得結(jié)果庙曙,就將Promise的狀態(tài)變?yōu)?code>reject,否則變?yōu)?code>resolve浩淘。
例 1.9 Promise.resolve()
**參數(shù)是一個Promise實例 **
如果參數(shù)是Promise實例 捌朴,那么Promise.resolve
將不做任何修改吴攒、原封不動地返回這個實例 。
參數(shù)是一個thenable對象
thenable對象指的是具有then方法的對象
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
Promise.resolve
方法會將這個對象轉(zhuǎn)為Promise對象砂蔽,然后就立即執(zhí)行thenable
對象的then
方法洼怔。
參數(shù)不是具有then方法的對象,或根本就不是對象
如果參數(shù)是一個原始值左驾,或者是一個不具有then方法的對象镣隶,則Promise.resolve方法返回一個新的Promise對象,狀態(tài)為Resolved诡右。
var p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
不帶有任何參數(shù)
Promise.resolve()
直接返回一個Resolve
狀態(tài)的Promise
對象
var p = Promise.resolve();
p.then(function () {
// ...
});
注意安岂,立即resolve
的Promise對象,是在本輪“事件循環(huán)”(event loop)的結(jié)束時帆吻,而不是在下一輪“事件循環(huán)”的開始時域那。
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
記住,有可能拋出錯誤的代碼都有可能因為錯誤被吞噬而對你的工作造成困擾猜煮。但是如果你用 Promise.resolve()
包裝了代碼的話次员,你永遠(yuǎn)都可以在代碼后面加上 catch()
。
例 1.10 Promise.reject()
Promise.reject(reason)
方法也會返回一個新的 Promise 實例 王带,該實例 的狀態(tài)為rejected
淑蔚。
注意,Promise.reject()
方法的參數(shù)愕撰,會原封不動地作為reject
的理由束倍,變成后續(xù)方法的參數(shù)。這一點與Promise.resolve方法不一致盟戏。
const thenable = {
then(resolve, reject) {
reject('出錯了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
// e參數(shù)是thenable對象
例 1.11 done()
Promise對象的回調(diào)鏈,不管以then
方法或catch
方法結(jié)尾甥桂,要是最后一個方法拋出錯誤柿究,都有可能無法捕捉到(因為Promise內(nèi)部的錯誤不會冒泡到全局)。因此黄选,我們可以提供一個done
方法蝇摸,總是處于回調(diào)鏈的尾端,保證拋出任何可能出現(xiàn)的錯誤办陷。
asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.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)的錯誤制圈,并向全局拋出们童,然后用unhandledRejection
捕獲畔况。
例 1.12 finally
finally
方法用于指定不管Promise對象最后狀態(tài)如何,都會執(zhí)行的操作慧库。它與done
方法的最大區(qū)別跷跪,它接受一個普通的回調(diào)函數(shù)作為參數(shù),該函數(shù)不管怎樣都必須執(zhí)行齐板。
下面是一個例子吵瞻,服務(wù)器使用Promise處理請求,然后使用finally
方法關(guān)掉服務(wù)器甘磨。
server.listen(0)
.then(function () {
// run test
})
.finally(server.stop);
實現(xiàn)方法
Promise.prototype.finally = function (cb) {
let P = this.constructor; // 返回對此對象 Promise 的引用
return this.then(
value => P.resolve(cb()).then(() => value),
reason => P.resolve(cb()).then(() => { throw reason })
)
}
上面代碼中橡羞,不管前面的Promise是fulfilled
還是rejected
,都會執(zhí)行回調(diào)函數(shù)cb
宽档。
例 1.13 Promise.try()
實際開發(fā)中尉姨,經(jīng)常遇到一種情況:不知道或者不想?yún)^(qū)分,函數(shù)f
是同步函數(shù)還是異步操作吗冤,但是想用 Promise 來處理它又厉。因為這樣就可以不管f
是否包含異步操作,都用then
方法指定下一步流程椎瘟,用catch
方法處理f拋出的錯誤覆致。
使用Promise.try()
const f = () => console.log('111');
Promise.try(f);
console.log('222');
// 111
// 222
由于Promise.try
為所有操作提供了統(tǒng)一的處理機(jī)制,所以如果想用then
方法管理流程肺蔚,最好都用Promise.try
包裝一下煌妈。這樣有許多好處,其中一點就是可以更好地管理異常宣羊。
function getUsername(userId) {
return database.users.get({id: userId})
.then(function(user) {
return user.name;
});
}
database.users.get()
可能會拋出同步錯誤或者異步錯誤璧诵。
// 拋出異步錯誤
database.users.get({id: userId})
.then(...)
.catch(...)
// 同步錯誤(比如數(shù)據(jù)庫連接錯誤,具體要看實現(xiàn)方法)
try {
database.users.get({id: userId})
.then(...)
.catch(...)
} catch (e) {
// ...
}
// 更優(yōu)雅的操作仇冯,相信你會喜歡的
Promise.try(database.users.get({id: userId}))
.then(...)
.catch(...)
事實上之宿,Promise.try
就是模擬try代碼塊,就像promise.catch
模擬的是catch
代碼塊苛坚。