Promise 的含義
Promise 是異步編程的一種解決方案禽拔,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大刘离。它由社區(qū)最早提出和實(shí)現(xiàn),ES6 將其寫進(jìn)了語言標(biāo)準(zhǔn)睹栖,統(tǒng)一了用法硫惕,原生提供了Promise對象。
從語法上說野来,Promise 是一個對象恼除,從它可以獲取異步操作的消息。
// 傳統(tǒng)寫法 回調(diào)地獄
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// ...
});
});
});
});
// Promise 的寫法
(new Promise(step1))
.then(step2)
.then(step3)
.then(step4);
Promise 對象的狀態(tài)
Promise 對象通過自身的狀態(tài)曼氛,來控制異步操作豁辉。Promise 實(shí)例具有三種狀態(tài)。
- 異步操作未完成(pending)
- 異步操作成功(fulfilled)
- 異步操作失斠ɑ肌(rejected)
上面三種狀態(tài)里面徽级,fulfilled和rejected合在一起稱為resolved(已定型)。
這三種的狀態(tài)的變化途徑只有兩種构舟。
- 從“未完成”到“成功”
- 從“未完成”到“失敗”
一旦狀態(tài)發(fā)生變化灰追,就凝固了,不會再有新的狀態(tài)變化狗超。這也是 Promise 這個名字的由來弹澎,它的英語意思是“承諾”,一旦承諾成效努咐,就不得再改變了苦蒿。
創(chuàng)建一個Promise對象
Promise 構(gòu)造函數(shù)
var promise = new Promise(function (resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else { /* 異步操作失敗 */
reject(new Error());
}
});
該函數(shù)的兩個參數(shù)分別是resolve和reject。它們是兩個函數(shù)渗稍,由 JavaScript 引擎提供佩迟,不用自己部署。
Promise.resolve 方法
將一個普通對象生成一個Promise對象
Promise.resolve()等價(jià)于下面的寫法竿屹。
Promise.resolve('foo')
// 等價(jià)于
new Promise(resolve => resolve('foo'))
Promise.reject 方法
生成一個新的 Promise 實(shí)例报强,該實(shí)例的狀態(tài)為rejected。
const p = Promise.reject('出錯了');
// 等同于
const p = new Promise((resolve, reject) => reject('出錯了'))
p.then(null, function (s) {
console.log(s)
});
// 出錯了
Promise 狀態(tài)的處理方法
Promise.prototype.then 方法
Promise 實(shí)例具有then方法拱燃,它的作用是為 Promise 實(shí)例添加狀態(tài)改變時(shí)的回調(diào)函數(shù)秉溉。
then方法的第一個參數(shù)是resolved狀態(tài)的回調(diào)函數(shù),第二個參數(shù)(可選)是rejected狀態(tài)的回調(diào)函數(shù)(該參數(shù)可以省略)。一旦狀態(tài)改變召嘶,就調(diào)用相應(yīng)的回調(diào)函數(shù)父晶。
下面是一個Promise對象的簡單例子。Promise 新建后就會立即執(zhí)行弄跌。
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('done')
}, ms);
});
}
timeout(100).then(value => {
console.log(value); // done
});
then方法返回的是一個新的Promise實(shí)例(注意甲喝,不是原來那個Promise實(shí)例)。因此可以采用鏈?zhǔn)綄懛踔唬磘hen方法后面再調(diào)用另一個then方法埠胖。
采用鏈?zhǔn)降膖hen,可以指定一組按照次序調(diào)用的回調(diào)函數(shù)淳玩。這時(shí)押袍,前一個回調(diào)函數(shù),有可能返回的還是一個Promise對象(即有異步操作)凯肋,這時(shí)后一個回調(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)
);
Promise 對象的報(bào)錯具有傳遞性圈盔,會一直向后傳遞,直到被捕獲為止悄雅。如果不設(shè)置回調(diào)函數(shù)驱敲,Promise內(nèi)部拋出的錯誤,不會反應(yīng)到外部宽闲。比如下面的例子:
p1
.then(step1)
.then(step2)
.then(step3)
.then(
console.log,
console.error
);
如果step1的狀態(tài)變?yōu)閞ejected众眨,那么step2和step3都不會執(zhí)行了(因?yàn)樗鼈兪莚esolved的回調(diào)函數(shù))。Promise 開始尋找容诬,接下來第一個為rejected的回調(diào)函數(shù)
Promise.prototype.catch 方法
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的別名娩梨,用于指定發(fā)生錯誤時(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狈定,則會調(diào)用then()方法指定的回調(diào)函數(shù);如果異步操作拋出錯誤习蓬,狀態(tài)就會變?yōu)閞ejected纽什,就會調(diào)用catch()方法指定的回調(diào)函數(shù),處理這個錯誤躲叼。另外芦缰,p.then()方法指定的回調(diào)函數(shù),如果運(yùn)行中拋出錯誤枫慷,也會被catch()方法捕獲让蕾。
一般來說包斑,不要在then()方法里面定義 Reject 狀態(tài)的回調(diào)函數(shù)(即then的第二個參數(shù)),總是使用catch方法涕俗。
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
上面代碼中,第二種寫法要好于第一種寫法神帅,理由是第二種寫法可以捕獲前面then方法執(zhí)行中的錯誤再姑,也更接近同步的寫法(try/catch)。因此找御,建議總是使用catch()方法元镀,而不使用then()方法的第二個參數(shù)。
Promise.prototype.finally 方法
finally()方法用于指定不管 Promise 對象最后狀態(tài)如何霎桅,都會執(zhí)行的操作栖疑。該方法是 ES2018 引入標(biāo)準(zhǔn)的。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
上面代碼中滔驶,不管promise最后的狀態(tài)遇革,在執(zhí)行完then或catch指定的回調(diào)函數(shù)以后,都會執(zhí)行finally方法指定的回調(diào)函數(shù)揭糕。
Nodejs中的Promise
nodejs8以上已經(jīng)原生支持es6語法書寫代碼了萝快,雖然 Promise 已經(jīng)普及,但是 Node.js 里仍然有大量的依賴回調(diào)的異步函數(shù)著角,
所以 Node8 就提供了 util.promisify() 這個方法揪漩,方便我們快捷的把原來的異步回調(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使得異步代碼看起來像同步代碼奄容,這正是它的魔力所在。
async/await是基于Promise實(shí)現(xiàn)的产徊,它不能用于普通的回調(diào)函數(shù)昂勒。
基本用法
async
async函數(shù)返回一個 Promise 對象。
async函數(shù)內(nèi)部return語句返回的值舟铜,會成為then方法回調(diào)函數(shù)的參數(shù)叁怪。
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
async函數(shù)內(nèi)部拋出錯誤,會導(dǎo)致返回的 Promise 對象變?yōu)閞eject狀態(tài)深滚。拋出的錯誤對象會被catch方法回調(diào)函數(shù)接收到奕谭。
async function f() {
throw new Error('出錯了');
}
f().then(
v => console.log('resolve', v),
e => console.log('reject', e)
)
//reject Error: 出錯了
await
await命令后面是一個 Promise 對象,返回該對象的結(jié)果痴荐。如果不是 Promise 對象血柳,就直接返回對應(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的寫法:
// 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('.');