什么是promise?
promise是js直接中進(jìn)行異步編程的新的解決方案
promise的基本使用
Promise構(gòu)造函數(shù)接受一個(gè)函數(shù)作為參數(shù),該函數(shù)的兩個(gè)參數(shù)分別是 resolve和reject 。它們是兩個(gè)函數(shù),由JavaScript引擎提供,不用自己部署。
創(chuàng)建一個(gè)promise
var p = new Promise((resolve,reject) => {
// 執(zhí)行器函數(shù)
1、成功 調(diào)用resolve(value)
2、失敗 調(diào)用reject(resaon)
})
p.then(
value => { // 接收得到的成功的value數(shù)據(jù)
},
reason => { // 接收得到的失敗的reason數(shù)據(jù)
}
)
promise的三種狀態(tài):ending 、resolved(又稱Fulfilled)、rejected
- promise 對(duì)象初始化狀態(tài)為 pending
- 當(dāng)調(diào)用resolve(成功),會(huì)由pending => resolved
- 當(dāng)調(diào)用reject(失敗),會(huì)由pending => rejected
注:promise一旦狀態(tài)改變,就不會(huì)再改,
一個(gè)promise指定多個(gè)成功/失敗的回調(diào)函數(shù),當(dāng)promise改變?yōu)閷?duì)應(yīng)狀態(tài)時(shí)都會(huì)調(diào)用
var p = new Promise((resolve,reject) => {
setTimeout(() => {
console.log('上課了')
resolve()
},10)
})
p.then(() => {
console.log('班長(zhǎng):起立')
})
p.then(() => {
console.log('同學(xué)們:老師好')
})
改變promise狀態(tài)與指定回調(diào)函數(shù)誰(shuí)先誰(shuí)后?
- 都有可能 正常情況下是先指定回調(diào)再改變狀態(tài),但也可以先改變狀態(tài)再指定回調(diào)
- 如何先改變狀態(tài),再指定回調(diào)
在執(zhí)行器函數(shù)中直接調(diào)用resolve/reject,延遲更長(zhǎng)時(shí)間才調(diào)用then() - 什么時(shí)候才能得到數(shù)據(jù)?
如果先指定回調(diào),當(dāng)狀態(tài)改變時(shí),回調(diào)就會(huì)先調(diào)用,得到數(shù)據(jù)
如果先改變狀態(tài),那當(dāng)指定回調(diào)時(shí),回調(diào)函數(shù)就會(huì)調(diào)用,得到數(shù)據(jù)
// 先指定回調(diào)函數(shù),后改變狀態(tài)
var p = new Promise((resolve,reject) => {
setTimeout(() => {
// 后改變狀態(tài),同時(shí)傳遞數(shù)據(jù),異步執(zhí)行回調(diào)函數(shù)
resolve('成功')
},3000)
}).then((value) => {
// 先指定回調(diào)函數(shù),保存當(dāng)前指定的回調(diào)函數(shù)
console.log('我是回調(diào)函數(shù)')
console.log(value)
})
// 先改變狀態(tài),后指定回調(diào)函數(shù)
var p = new Promise((resolve, reject) => {
// 先改變狀態(tài),同時(shí)傳遞數(shù)據(jù)
resolve('馬上就成功')
})
setTimeout(() => {
// 后指定回調(diào)函數(shù),異步執(zhí)行回調(diào)函數(shù)
p.then((value) => {
console.log(value)
})
},1000)
Promise.prototype.then()
Promise實(shí)例生成以后,可以用then方法分別指定Resolved狀態(tài)和Reject狀態(tài)的回調(diào)函數(shù)。then 方法可以接受兩個(gè)回調(diào)函數(shù)作為參數(shù)。第一個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)镽esolved時(shí)調(diào)用,第二個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)?Reject時(shí)調(diào)用。其中,第二個(gè)函數(shù)是可選的,不一定要提供。]
then方法返回的是一個(gè)新的Promise實(shí)例(注意,不是原來(lái)那個(gè)Promise實(shí)例)。因此可以采用鏈?zhǔn)綄懛?,即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)用。
promise.then()返回新的peomise的結(jié)果狀態(tài)由前一個(gè)指定的回調(diào)函數(shù)執(zhí)行的結(jié)果決定:
- 如果拋出異常,新promise變?yōu)閞esolved ,reason為拋出的異常
- 如果返回的是非promise的任意值,新peomise變?yōu)閞esolved,value為返回的值
- 如果返回的是另一個(gè)新的peomise,此promise的結(jié)果會(huì)成為新的promise的結(jié)果
var p = new Promise((resolve,reject) => {
resolve('成功')
})
.then(
value => {
// 沒(méi)有指定返回值,是一個(gè)非promise的任意值
console.log('resolve1',value)
},reason => {
console.log('reject1', reason)
})
.then(
value => {
// 根據(jù)上一個(gè)函數(shù)的結(jié)果,狀態(tài)為resolved, 調(diào)用
console.log('resolve2', value)
}, reason => {
console.log('reject2', reason)
})
// resolve1 成功
// resolve2 undefined
var p = new Promise((resolve,reject) => {
resolve('成功')
})
.then(
value => {
// 根據(jù)執(zhí)行器函數(shù)結(jié)果,調(diào)用成功函數(shù)
console.log('resolve1',value)
throw '失敗了'
},reason => {
console.log('reject1', reason)
})
.then(
value => {
console.log('resolve2', value)
}, reason => {
// 根據(jù)上一個(gè)then的函數(shù)返回結(jié)果,調(diào)用reject,reason為錯(cuò)誤原因
console.log('reject2', reason)
})
// resolve1 成功
// reject2 失敗了
如何中斷promise鏈?
- 當(dāng)使用promise的then鏈?zhǔn)秸{(diào)用時(shí),在中間中斷,不再調(diào)用后面的回調(diào)函數(shù)
辦法:在回調(diào)函數(shù)中返回一個(gè)pendding狀態(tài)的promise對(duì)象
new Promise((resolve, reject) => {
reject(1)
}).then(
value => console.log(value)
).catch(
reason => {
console.log('程序報(bào)錯(cuò)就中斷promise')
return new Promise(() => { }) // 返回一個(gè)pending的promise
})
.then(
value => console.log('如果程序報(bào)錯(cuò),將不會(huì)執(zhí)行到這')
)
Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)。
如果Promise狀態(tài)已經(jīng)變成Resolved,再拋出錯(cuò)誤是無(wú)效的。
如果沒(méi)有使用catch方法指定錯(cuò)誤處理的回調(diào)函數(shù),Promise對(duì)象拋出的錯(cuò)誤不會(huì)傳遞到外層代碼,即不會(huì)有任何 反應(yīng)。
Promise對(duì)象的錯(cuò)誤具有“冒泡”性質(zhì),會(huì)一直向后傳遞,直到被捕獲為止。也就是說(shuō),錯(cuò)誤總是會(huì)被下一個(gè)catch語(yǔ)句捕獲。
catch方法返回的還是一個(gè)Promise對(duì)象,因此后面還可以接著調(diào)用then方法。
鏈?zhǔn)秸{(diào)用中,如果沒(méi)有報(bào)錯(cuò),會(huì)跳過(guò)catch方法,繼續(xù)后面的then()方法,要是then方法里面報(bào)錯(cuò),就與前面的catch無(wú)關(guān)了。
var p = new Promise((resolved,reject) => {
reject('失敗了')
resolved('你看我在哪?') // 永遠(yuǎn)不會(huì)被調(diào)用
}).then(( value ) => {
console.log(1)
}).then((value) => {
console.log(2)
}).catch((error) => {
// 處理前面三個(gè)Promise產(chǎn)生的錯(cuò)誤
console.log(error)
}).then(() => {
console.log('我是可以被調(diào)用的')
});
return 一個(gè) error 對(duì)象并不會(huì)拋出錯(cuò)誤,所以不會(huì)被后續(xù)的 .catch 捕獲
Promise.resolve()
.then(() => {
return new Error('error!!!')
})
.then((res) => {
console.log('then: ', res)
})
.catch((err) => {
console.log('catch: ', err)
})
// then: Error: error!!!
// at Promise.resolve.then (...)
// at ...
Promise.all()
Promise.all方法用于將多個(gè)Promise實(shí)例,包裝成一個(gè)新的Promise實(shí)例。且返回一個(gè)promise
p 的狀態(tài)由 p1 、p2、p3決定,分成兩種情況。
只有 p1 、p2、p3的狀態(tài)都變成 fulfilled, p 的狀態(tài)才會(huì)變成fulfilled ,此時(shí) p1 、p2、p3 的返回值組成一個(gè)數(shù)組,傳遞給 p 的回調(diào)函數(shù)。
只要p1 、p2、p3之中有一個(gè)被rejected, p 的狀態(tài)就變成rejected ,此時(shí)第一個(gè)被 reject實(shí)例的返回值,會(huì)傳遞給p的回調(diào)函數(shù)。
let p1 = new Promise((resolve, reject) => {
resolve(1);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
},100)
});
let p3 = new Promise((resolve, reject) => {
resolve(3);
});
Promise.all([p1, p2, p3])
.then(data => {
console.log(data);
// [1, 2, 3] 結(jié)果順序和promise實(shí)例數(shù)組順序是一致的
})
.catch(error => {
console.log(data);
})
Promise.race()
Promise.race()是參數(shù)中有一個(gè)實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變,那個(gè)率先改變的Promise實(shí)例的返回值,就傳遞給p的回調(diào)函數(shù)。
Promise.race()方法,同樣是將多個(gè)Promise實(shí)例,包裝成一個(gè)新的Promise實(shí)例。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 100)
});
let p2 = new Promise((resolve, reject) => {
resolve(2);
});
let p3 = new Promise((resolve, reject) => {
resolve(3);
});
Promise.race([p1, p2, p3])
.then(data => {
console.log(data);
// 2 返回那個(gè)率先改變的Promise實(shí)例的返回值
})
.catch(error => {
console.log(data);
})
Promise.resolve()
主要作用:將現(xiàn)有對(duì)象轉(zhuǎn)為Promise對(duì)象
Promise.resolve 方法的參數(shù)分成四種情況
參數(shù)是一個(gè)Promise實(shí)例
如果參數(shù)是Promise實(shí)例,那么Promise.resolve將不做任何修改、原封不動(dòng)地返回這個(gè)實(shí)例參數(shù)是一個(gè)thenable對(duì)象
thenable對(duì)象指的是具有then方法的對(duì)象,Promise.resolve方法會(huì)將這個(gè)對(duì)象轉(zhuǎn)為Promise對(duì)象,然后就立即執(zhí)行thenable對(duì)象的then方法。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
- 參數(shù)不是具有then方法的對(duì)象,或者根本不是對(duì)象
如果參數(shù)是一個(gè)原始值,或者是一個(gè)不具有then方法的對(duì)象,則Promise.resolve方法返回一個(gè)新的Promise對(duì)象,狀態(tài)為Resolved。
var p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
- 不帶有任何參數(shù)
Promise.resolve方法允許調(diào)用時(shí)不帶參數(shù),直接返回一個(gè)Resolved狀態(tài)的Promise對(duì)象
var p = Promise.resolve();
p.then(function () {
// ...
});
需要注意的是,立即resolve的Promise對(duì)象,是在本輪“事件循環(huán)”(event loop)的結(jié)束時(shí),而不是在下一輪“事件循環(huán)”的開始時(shí)。
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
Promise.reject()
Promise.reject(reason)方法也會(huì)返回一個(gè)新的Promise實(shí)例,該實(shí)例的狀態(tài)為rejected。它的參數(shù)用法與Promise.resolve方法完全一致。
實(shí)現(xiàn)一個(gè)基本的promise
// 自定義promise函數(shù)模塊:IIFE
(function (window) {
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
// promise構(gòu)造函數(shù)
// excutor執(zhí)行器函數(shù)(同步執(zhí)行)
function Promise(excutor) {
var self = this;
this.status = PENDING // 給promise對(duì)象指定status屬性,初始值為pending
this.data = undefined // 給promise對(duì)象指定一個(gè)用戶存儲(chǔ)結(jié)果數(shù)據(jù)的屬性
this.callbacks = [] // 每個(gè)元素的結(jié)構(gòu){ onResolved(){}, onReject(){}}
// 立即同步執(zhí)行excutor
function resolve(value) {
// 如果當(dāng)前狀態(tài)不是pending,直接結(jié)束
if (self.status !== PENDING) {
return
}
// 將狀態(tài)改為resolved
self.status = RESOLVED
// 保存value數(shù)據(jù)
self.data = value
// 如果有待執(zhí)行的callback函數(shù),立即異步執(zhí)行回調(diào)函數(shù)onResolved
if (self.callbacks.length > 0) {
// 異步執(zhí)行
setTimeout(() => {
self.callbacks.forEach(callbacksObj => {
callbacksObj.onResolved(value)
})
})
}
}
function reject(reason) {
// 如果當(dāng)前狀態(tài)不是pending,直接結(jié)束
if (self.status !== PENDING) {
return
}
// 將狀態(tài)改為resolved
self.status = REJECTED
// 保存value數(shù)據(jù)
self.data = reason
// 如果有待執(zhí)行的callback函數(shù),立即異步執(zhí)行回調(diào)函數(shù)onRejected
if (self.callbacks.length > 0) {
// 異步執(zhí)行
setTimeout(() => {
self.callbacks.forEach(callbacksObj => {
callbacksObj.onRejected(reason)
})
})
}
}
try {
excutor(resolve, reject)
} catch(error){
// 如果執(zhí)行器出現(xiàn)異常,promise對(duì)象變?yōu)閞eject狀態(tài)
reject(error)
}
}
// promise原型對(duì)象的then方法
// 指定成功和失敗的回調(diào)函數(shù)
// 返回一個(gè)新的promise對(duì)象
Promise.prototype.then = function (onResolved, onRejected) {
// 向后傳遞成功的value
onResolved = typeof onResolved === 'function' ? onResolved : value => value
// 指定默認(rèn)的失敗的回調(diào)(實(shí)現(xiàn)錯(cuò)誤/異常穿透的關(guān)鍵點(diǎn)) 向后傳遞失敗的reason
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}
const self = this
// console.log(self.data)
return new Promise((resolve, reject) => {
// 調(diào)用指定的函數(shù)處理
// 根據(jù)執(zhí)行結(jié)果,改變r(jià)eturn的promise的狀態(tài)
function handle(callback) {
// 1、如果拋出異常,return的promise就會(huì)失敗,reason就是error
// 2、如果回調(diào)函數(shù)返回非promise,return的promise就會(huì)成功,value就是返回值
// 3、如果回調(diào)函數(shù)返回的是promise,return的promise結(jié)果就是這個(gè)promise的結(jié)果
try {
const result = callback(self.data)
// 3、如果回調(diào)函數(shù)返回的是promise,return的promise結(jié)果就是這個(gè)promise的結(jié)果
if (result instanceof Promise) {
// result.then(
// value => {
// resolve(value) // 當(dāng)result成功時(shí),return的promise就會(huì)成功,value就是返回的值
// },
// reason => {
// reject(reason) // 當(dāng)result失敗時(shí),讓return的promise也失敗
// }
// )
// 或者直接
result.then(resolve,reject)
} else {
// 2、如果回調(diào)函數(shù)返回非promise,return的promise就會(huì)成功,value就是返回值
resolve(result)
}
} catch (error) {
// 1、如果拋出異常,return的promise就會(huì)失敗,reason就是error
reject(error)
}
}
if (self.status === PENDING) {
// 當(dāng)前狀態(tài)還是peding狀態(tài),將回調(diào)函數(shù)保存起來(lái)
self.callbacks.push({
onResolved(value) {
handle(onResolved)
},
onRejected(reason) {
handle(onRejected)
}
})
} else if (self.status === RESOLVED) {
// 如果當(dāng)前是resolved 異步執(zhí)行onResolved并改變r(jià)eturn的promise狀態(tài)
setTimeout(() => {
// handle(onRejected)
handle(onResolved)
})
} else { // reject
// 如果當(dāng)前是resolved 異步執(zhí)行onRejectd并改變r(jià)eturn的promise狀態(tài)
setTimeout(() => {
handle(onRejected)
})
}
})
}
// promise原型對(duì)象的catch方法
// 指定失敗的回調(diào)函數(shù)
// 返回一個(gè)新的promise對(duì)象
Promise.prototype.catch = function (onRejected) {
return this.then(undefined,onRejected)
}
// promise函數(shù)對(duì)象的resolve方法
// 返回一個(gè)指定結(jié)果的成功的promise
Promise.resolve = function (value) {
// 如果是一般值
// 如果是成功的promise
// 如果是失敗的promise
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(resolve, reject)
} else {
// value不是promise =》 promise 變成功,數(shù)據(jù)是value
resolve(value)
}
})
}
// promise函數(shù)對(duì)象的reject方法
// 返回一個(gè)指定reason的成功的promise
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
// promise函數(shù)對(duì)象的all方法
// 返回一個(gè)promise,只有當(dāng)所有的promise都成功時(shí)才算成功,否則失敗
Promise.all = function (promises) {
// 用來(lái)保存所有成功value數(shù)組
const values = new Array(promises.length)
// 計(jì)數(shù)器
let resolvedCount = 0
return new Promise((resolve, reject) => {
// 遍歷獲取每一個(gè)promise的結(jié)果
promises.forEach((p, index) => {
Promise.resolve(p).then(
value => {
resolvedCount++
// p成功,將成功的value保存values
value[index] = value
// 如果全部成功了,將return的promise改變成功
if (resolvedCount === promises.length) {
resolve(values)
}
},
reason => {
reject(reason)
}
)
})
})
}
// promise函數(shù)對(duì)象的race方法
// 返回一個(gè)promise,其結(jié)果由第一個(gè)完成的promise決定
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
promises.forEach((p, index) => {
// Promise.resolve(p)為了兼容傳入的不是一個(gè)promise
Promise.resolve(p).then(
value => {
resolve(value)
},
reason => {
reject(reason)
}
)
})
})
}
// 返回一個(gè)promise對(duì)象,在指定的時(shí)間后再確認(rèn)結(jié)果
Promise.resolveDelay = function (value,time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// value是promise
if (value instanceof Promise) {
value.then(resolve, reject)
} else {
// value不是promise =》 promise 變成功,數(shù)據(jù)是value
resolve(value)
}
}, time)
})
}
Promise.rejectDelsy = function (reason, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(value)
}, time)
})
}
// 向外暴露promise函數(shù)
window.Promise = Promise
})(window)
關(guān)于promise的經(jīng)典面試題
選了幾篇有質(zhì)量的promise面試題,可以做做:
1、Promise面試題
2、Promise 必知必會(huì)(十道題)
3、面試官眼中的Promise
本文參考:
《es6標(biāo)準(zhǔn)教程》
《promise核心技術(shù)》