Promise 的含義
Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大。它由社區(qū)最早提出和實(shí)現(xiàn),ES6 將其寫(xiě)進(jìn)了語(yǔ)言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了Promise對(duì)象。
從語(yǔ)法上說(shuō),Promise 是一個(gè)對(duì)象,從它可以獲取異步操作的消息。
// 傳統(tǒng)寫(xiě)法 回調(diào)地獄
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// ...
});
});
});
});
// Promise 的寫(xiě)法
(new Promise(step1))
.then(step2)
.then(step3)
.then(step4);
Promise 對(duì)象的狀態(tài)
Promise 對(duì)象通過(guò)自身的狀態(tài),來(lái)控制異步操作。Promise 實(shí)例具有三種狀態(tài)。
- 異步操作未完成(pending)
- 異步操作成功(fulfilled)
- 異步操作失敗(rejected)
上面三種狀態(tài)里面,fulfilled和rejected合在一起稱為resolved(已定型)。
這三種的狀態(tài)的變化途徑只有兩種。
- 從“未完成”到“成功”
- 從“未完成”到“失敗”
一旦狀態(tài)發(fā)生變化,就凝固了,不會(huì)再有新的狀態(tài)變化。這也是 Promise 這個(gè)名字的由來(lái),它的英語(yǔ)意思是“承諾”,一旦承諾成效,就不得再改變了。
創(chuàng)建一個(gè)Promise對(duì)象
Promise 構(gòu)造函數(shù)
var promise = new Promise(function (resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else { /* 異步操作失敗 */
reject(new Error());
}
});
該函數(shù)的兩個(gè)參數(shù)分別是resolve和reject。它們是兩個(gè)函數(shù),由 JavaScript 引擎提供,不用自己部署。
Promise.resolve 方法
將一個(gè)普通對(duì)象生成一個(gè)Promise對(duì)象
Promise.resolve()等價(jià)于下面的寫(xiě)法。
Promise.resolve('foo')
// 等價(jià)于
new Promise(resolve => resolve('foo'))
Promise.reject 方法
生成一個(gè)新的 Promise 實(shí)例,該實(shí)例的狀態(tài)為rejected。
const p = Promise.reject('出錯(cuò)了');
// 等同于
const p = new Promise((resolve, reject) => reject('出錯(cuò)了'))
p.then(null, function (s) {
console.log(s)
});
// 出錯(cuò)了
Promise 狀態(tài)的處理方法
Promise.prototype.then 方法
Promise 實(shí)例具有then方法,它的作用是為 Promise 實(shí)例添加狀態(tài)改變時(shí)的回調(diào)函數(shù)。
then方法的第一個(gè)參數(shù)是resolved狀態(tài)的回調(diào)函數(shù),第二個(gè)參數(shù)(可選)是rejected狀態(tài)的回調(diào)函數(shù)(該參數(shù)可以省略)。一旦狀態(tài)改變,就調(diào)用相應(yīng)的回調(diào)函數(shù)。
下面是一個(gè)Promise對(duì)象的簡(jiǎn)單例子。Promise 新建后就會(huì)立即執(zhí)行。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('done')
}, ms);
});
}
timeout(100).then(value => {
console.log(value); // done
});
then方法返回的是一個(gè)新的Promise實(shí)例(注意,不是原來(lái)那個(gè)Promise實(shí)例)。因此可以采用鏈?zhǔn)綄?xiě)法,即then方法后面再調(diào)用另一個(gè)then方法。
采用鏈?zhǔn)降膖hen,可以指定一組按照次序調(diào)用的回調(diào)函數(shù)。這時(shí),前一個(gè)回調(diào)函數(shù),有可能返回的還是一個(gè)Promise對(duì)象(即有異步操作),這時(shí)后一個(gè)回調(diào)函數(shù),就會(huì)等待該P(yáng)romise對(duì)象的狀態(tài)發(fā)生變化,才會(huì)被調(diào)用。
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("resolved: ", comments),
err => console.log("rejected: ", err)
);
Promise 對(duì)象的報(bào)錯(cuò)具有傳遞性,會(huì)一直向后傳遞,直到被捕獲為止。如果不設(shè)置回調(diào)函數(shù),Promise內(nèi)部拋出的錯(cuò)誤,不會(huì)反應(yīng)到外部。比如下面的例子:
p1
.then(step1)
.then(step2)
.then(step3)
.then(
console.log,
console.error
);
如果step1的狀態(tài)變?yōu)閞ejected,那么step2和step3都不會(huì)執(zhí)行了(因?yàn)樗鼈兪莚esolved的回調(diào)函數(shù))。Promise 開(kāi)始尋找,接下來(lái)第一個(gè)為rejected的回調(diào)函數(shù)
Promise.prototype.catch 方法
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(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));
上面代碼中,如果p的狀態(tài)變?yōu)閞esolved,則會(huì)調(diào)用then()方法指定的回調(diào)函數(shù);如果異步操作拋出錯(cuò)誤,狀態(tài)就會(huì)變?yōu)閞ejected,就會(huì)調(diào)用catch()方法指定的回調(diào)函數(shù),處理這個(gè)錯(cuò)誤。另外,p.then()方法指定的回調(diào)函數(shù),如果運(yùn)行中拋出錯(cuò)誤,也會(huì)被catch()方法捕獲。
一般來(lái)說(shuō),不要在then()方法里面定義 Reject 狀態(tài)的回調(diào)函數(shù)(即then的第二個(gè)參數(shù)),總是使用catch方法。
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
上面代碼中,第二種寫(xiě)法要好于第一種寫(xiě)法,理由是第二種寫(xiě)法可以捕獲前面then方法執(zhí)行中的錯(cuò)誤,也更接近同步的寫(xiě)法(try/catch)。因此,建議總是使用catch()方法,而不使用then()方法的第二個(gè)參數(shù)。
Promise.prototype.finally 方法
finally()方法用于指定不管 Promise 對(duì)象最后狀態(tài)如何,都會(huì)執(zhí)行的操作。該方法是 ES2018 引入標(biāo)準(zhǔn)的。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
上面代碼中,不管promise最后的狀態(tài),在執(zhí)行完then或catch指定的回調(diào)函數(shù)以后,都會(huì)執(zhí)行finally方法指定的回調(diào)函數(shù)。
Nodejs中的Promise
nodejs8以上已經(jīng)原生支持es6語(yǔ)法書(shū)寫(xiě)代碼了,雖然 Promise 已經(jīng)普及,但是 Node.js 里仍然有大量的依賴回調(diào)的異步函數(shù),
所以 Node8 就提供了 util.promisify() 這個(gè)方法,方便我們快捷的把原來(lái)的異步回調(diào)方法改成返回 Promise 實(shí)例的方法
// 異步回調(diào)形式
fs.readFile('./test.js',function(err, data){
console.log(data)
})
// promisify 形式
const readFileAsync = Promise.promisify(fs.readFile)
readFileAsync('./test.js').then(function(data){
console.log(data)
}).catch(err){
console.log(err)
}
Async/Await
ES2017(ES8) 標(biāo)準(zhǔn)引入了 async 函數(shù),使得異步操作變得更加方便。async/await使得異步代碼看起來(lái)像同步代碼,這正是它的魔力所在。
async/await是基于Promise實(shí)現(xiàn)的,它不能用于普通的回調(diào)函數(shù)。
基本用法
async
async函數(shù)返回一個(gè) Promise 對(duì)象。
async函數(shù)內(nèi)部return語(yǔ)句返回的值,會(huì)成為then方法回調(diào)函數(shù)的參數(shù)。
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
async函數(shù)內(nèi)部拋出錯(cuò)誤,會(huì)導(dǎo)致返回的 Promise 對(duì)象變?yōu)閞eject狀態(tài)。拋出的錯(cuò)誤對(duì)象會(huì)被catch方法回調(diào)函數(shù)接收到。
async function f() {
throw new Error('出錯(cuò)了');
}
f().then(
v => console.log('resolve', v),
e => console.log('reject', e)
)
//reject Error: 出錯(cuò)了
await
await命令后面是一個(gè) Promise 對(duì)象,返回該對(duì)象的結(jié)果。如果不是 Promise 對(duì)象,就直接返回對(duì)應(yīng)的值。
await只能用在async方法內(nèi)部。
async function f() {
let result = await Promise.resolve('hello world');
console.log(result) // hello world
}
Promise與async/await
promise改造為async/await的寫(xiě)法:
// promise
const stat = util.promisify(fs.stat);
stat('.')
.then((stats) => {
// Do something with `stats`
})
.catch((error) => {
// Handle the error.
});
// async/await
const stat = util.promisify(fs.stat);
async function readStats(dir) {
try {
let stats = await stat(dir);
// Do something with `stats`
} catch (err) { // Handle the error.
console.log(err);
}
}
readStats('.');