目錄 (づ ̄ 3 ̄)づ=> So沒有一個目錄真的很惱火啄育。勘畔。
[TOC]
序
本文會對Promise規(guī)范進(jìn)行一個比較完整的實(shí)現(xiàn)检吆,目的是為了加深對Promise各個特性的理解從而更好的應(yīng)用蔬将。
[warning] 注意:本文依據(jù)Promises/A+規(guī)范進(jìn)行Promise的實(shí)現(xiàn)
1.Promise/A+ 術(shù)語
1.1. promise
promise是一個對象或則函數(shù)危融,它的表現(xiàn)是依據(jù)Promises/A+這篇規(guī)范說明來定義的挖胃。
1.1. promise
is an object or function with a then method whose behavior conforms to this specification.
1.2. theable
thenable是一個定義了then方法的對象或則函數(shù)屋休。
thenable
is an object or function that defines a then method.
1.3. value
value可以是任何合法的JS值坞古,甚至包括undefined、一個thenable劫樟、一個promise痪枫。
value
is any legal JavaScript value (including undefined, a thenable, or a promise).
1.4. exception
exception是一個用throw語句拋出的值。
exception
is a value that is thrown using the throw statement.
1.5. reason
reason是一個為什么promise會被拒絕的理由叠艳。
reason
is a value that indicates why a promise was rejected.
Promise規(guī)范要求
判斷一個東東是不是Promise奶陈,有三項(xiàng)主要的特征可作為參考
- Promise有三種狀態(tài)
pending
、fulfilled
附较、rejected
- Promise含有then方法
- Promise含有
Promise Resolution Procedure
(promise的狀態(tài)轉(zhuǎn)換處理方法)吃粒。
2.1. Promise狀態(tài)
一個promise必須處于 pending 、fulfilled拒课、rejected 三種狀態(tài)中的其中一種
下面是一個promise最基本的使用demo徐勃,我們先有個印象。
- 其中promise實(shí)例化的時候傳入了一個函數(shù)作為參數(shù)捕发,這個函數(shù)我們稱之為
executor
疏旨,它能告訴我們何時將promise狀態(tài)從pending轉(zhuǎn)化為其余兩態(tài)中的一態(tài)。 - 而
then
方法是實(shí)例化對象下的一個方法扎酷,它能傳入兩個參數(shù)檐涝,一般是兩個回調(diào)函數(shù),對應(yīng)fulfilled和rejected兩個狀態(tài),當(dāng)promise從pengding狀態(tài)轉(zhuǎn)化成其中一個狀態(tài)時就會觸發(fā)對應(yīng)的回調(diào)函數(shù)谁榜。
let p = new Promise((resolve,reject)=>{
let x = Math.random();
console.log(x);
if (x > .5) {
resolve('我是你許下的諾言的那個東東');
} else {
reject('我是你未能實(shí)現(xiàn)諾言的理由');
}
});
p.then((value)=>{ //綁定成功時的回調(diào)函數(shù)
console.log('fulfilled:',value); //fulfilled:我是你許下的諾言的那個東東
},(reason)=>{ //綁定失敗時的回調(diào)函數(shù)
console.log('rejected:',reason); //rejected:我是你未能實(shí)現(xiàn)諾言的理由
});
2.1.1. pending狀態(tài)
當(dāng)Promise處于pending狀態(tài)時幅聘,它可能轉(zhuǎn)換為fulfilled或則rejected狀態(tài)。
When pending, a promise:may transition to either the fulfilled or rejected state.
2.1.2. fulfilled狀態(tài)
當(dāng)Promise處于fulfilled狀態(tài)時窃植,它不再能轉(zhuǎn)換為其它狀態(tài) 且 它必須有一個值帝蒿,這個值不能被更改。
When fulfilled, a promise:
- must not transition to any other state.
- must have a value, which must not change.
2.1.3 rejected狀態(tài)
當(dāng)promise處于rejected時巷怜,它不再能轉(zhuǎn)換為其它狀態(tài) 且 它必須有一個理由葛超,這個理由不能被更改。
When rejected, a promise:
- must not transition to any other state.
- must have a reason, which must not change.
[danger]注意: 當(dāng)promise處于fulfilled或則rejected時延塑,它都有一個值绣张,這個值不能被更改,但是可以像使用常量一樣在這個值下面掛載其它值关带。
Here, “must not change” means immutable identity (i.e. ===), but does not imply deep immutability.
2.1. Promise實(shí)現(xiàn)
請先回顧一下我們在說Promise狀態(tài)時候最初的那個demo
我們通過實(shí)例化Promise時傳入了一個參數(shù)侥涵,這個參數(shù)是一個執(zhí)行函數(shù)(executor),它能決定什么時候?qū)romise轉(zhuǎn)換成fulfilled什么時候轉(zhuǎn)換成rejected宋雏。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function Promise(executor){
let self = this; //緩存下
self.value = undefined; //用來存放value和reason,因?yàn)閜romise只會處于一種狀態(tài)故可只用一個變量來表示芜飘。
self.status = PENDING; //將初始狀態(tài)設(shè)置為pending
self.onFulfilledCallbacks = []; //用來存放所有成功的回調(diào)函數(shù)
self.onRejectedCallbacks = []; //用來存放所有失敗的回調(diào)函數(shù)
try{
executor(resolve,reject); //調(diào)用執(zhí)行函數(shù),將resolve和reject方法作為參數(shù)傳入
}catch (e){
reject(e); //若執(zhí)行函數(shù)中存在異常直接用拋出的值來拒絕promise
}
//-----------------------------------------------------------------------------------------------------------
function resolve(value){ //此方法會隨著executor傳入而傳入
setTimeout(function(){
if(self.status === PENDING){ //確保狀態(tài)只會改變一次
self.status = FULFILLED; //改變狀態(tài)
self.value = value; //賦予一個值
self.onFulfilledCallbacks.forEach(cb => cb(self.value)); //2.2.2. //2.2.6.
}
})
}
function reject(reason){
setTimeout(function(){
if(self.status === PENDING){
self.status = REJECTED;
self.value = reason;
self.onRejectedCallbacks.forEach(cb => cb(self.value));
}
})
}
}
以上實(shí)現(xiàn)了2.1. 磨总,promise的三種狀態(tài)以及狀態(tài)之間的改變嗦明。
executor,形參舍败、實(shí)參招狸、作用域鏈
我們可以發(fā)現(xiàn)最終轉(zhuǎn)換狀態(tài)時通過Promise內(nèi)部的兩個方法resolve和reject,這個兩個方法是在什么時候傳入的呢邻薯?
一個函數(shù)的參數(shù)查找裙戏,是從調(diào)用這個函數(shù)時所處的作用域開始查找的。
new Promise傳入的executor厕诡,是參數(shù)也是對executor函數(shù)的定義累榜,此時executor的resolve和reject為形參。
我們new Promise的時候灵嫌,會執(zhí)行構(gòu)造函數(shù)Promise內(nèi)的代碼壹罚,也就是在這時executor被執(zhí)行,而executor此時所處的作用域是在Promise構(gòu)造函數(shù)內(nèi)部寿羞,resolve和reject方法作為實(shí)參被傳入猖凛。
2.2. then方法
一個promise必須提供一個then方法來使用它將要或則說已經(jīng)被賦予的 value 或則 reason,一個promise的then方法接收兩個參數(shù)
promise.then(onFulfilled,onRejected)
2.2.1. then參數(shù)
then中的參數(shù)皆為可選參數(shù)绪穆,如果onFulfilled或則說onRejected不是一個函數(shù)辨泳,那么將會被忽略虱岂。
Both onFulfilled and onRejected are optional arguments:
- If onFulfilled is not a function, it must be ignored.
- If onRejected is not a function, it must be ignored.
2.2.2. 如果onFulfilled是一個函數(shù)
- 如果onFulfilled是一個函數(shù),它必須在promise狀態(tài)轉(zhuǎn)換為fulfilled時候就被調(diào)用菠红,并且promise被賦予的value會成為這個函數(shù)(onFulfilled)的第一個參數(shù)第岖。
- onFulfilled不能在promise狀態(tài)轉(zhuǎn)化為fulfilled前就調(diào)用
- onFulfilled函數(shù)不能重復(fù)調(diào)用
原文規(guī)范詳見Promises/A+
2.2.3. 如果onRejected是一個函數(shù)
- 如果onRejected是一個函數(shù),它必須在promise狀態(tài)轉(zhuǎn)換為rejected時候就被調(diào)用试溯,并且promise被賦予的reason會成為這個函數(shù)(onRejected)的第一個參數(shù)蔑滓。
- onRejected不能在promise狀態(tài)轉(zhuǎn)化為rejected前就調(diào)用
- onRejected函數(shù)不能重復(fù)調(diào)用
2.2.4. onFulfilled 或則 onRejected 必須在執(zhí)行棧 只存在 platform code
時才能被調(diào)用。
2.2.5. onFulfilled 和 onRejected 必須被當(dāng)做函數(shù)調(diào)用遇绞。
2.2.6. 同一個promise實(shí)例可以調(diào)用多次then
- 當(dāng)一個promise轉(zhuǎn)化為fulfilled狀態(tài)键袱,所有onFulfilled callback會按照回調(diào)函數(shù)通過then添加時的順序而執(zhí)行。
- 當(dāng)一個promise轉(zhuǎn)化為rejected狀態(tài)摹闽,所有onRejected callback會按照回調(diào)函數(shù)通過then添加時的順序而執(zhí)行杠纵。
釋:then在同一個promise實(shí)例下多次調(diào)用,意味著可以在同一個promise的同一種狀態(tài)下綁定多個不同的回調(diào)函數(shù)钩骇,而這些回調(diào)函數(shù)執(zhí)行的順序和它們被綁定時的順序相同。
2.2.7. then必會返回一個新的promise
promise2 = promise1.then(onFulfilled,onRejected);
- 如果onFulfilled或onRejected回調(diào)函數(shù)中返回了一個值铝量,假定為x倘屹,那么調(diào)用一個 promise解析方法
[[Resolve]](promise2,x)
。 - 如果onFulfilled或者onRejected拋出了一個
exception
(異常)e
,promise2
必須以這個e作為reason來拒絕promise慢叨,使其狀態(tài)改變?yōu)閞ejected纽匙。 - 如果onFulfilled不是一個函數(shù)且
promise1
的狀態(tài)為fulfilled,promise2
必須以promise1
的值來fulfilled拍谐。 - 如果onRejected不是一個函數(shù)且
promise1
的狀態(tài)為rejected烛缔,promise2
必須以promise1
的理由來rejected。
2.2. Promise實(shí)現(xiàn)
2.2.提的是一個then的實(shí)現(xiàn)規(guī)則轩拨,而then主要作用為promise綁定回調(diào)函數(shù)践瓷,當(dāng)promise轉(zhuǎn)換狀態(tài)時會自動調(diào)用對應(yīng)的回調(diào)函數(shù)。(對應(yīng)規(guī)范2.2.2-2.2.3)
其實(shí)就是發(fā)布訂閱模式啦
function Promise(){
...
function resolve(value){
setTimeout(function(){ //2.2.4.
if(self.status === PENDING){ //2.2.2.3-2.2.2.4
self.status = FULFILLED;
self.value = value;
self.onFulfilledCallbacks.forEach(cb => cb(self.value)); //2.2.6.
}
})
}
function reject(reason){
setTimeout(function(){
if(self.status === PENDING){ //2.2.3.3-2.2.3.4
self.status = REJECTED;
self.value = reason;
self.onRejectedCallbacks.forEach(cb => cb(self.value));
}
})
}
}
//---------------------------------------------------------------------------------------------------
Promise.prototype.then = function (onFulfilled, onRejected) { //2.2.1.
//2.2.7.3-2.2.7.4 //2.2.5.
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
let self = this,
promise2; //2.2.7.0 //聲明要返回的promise2
if(self.status === PENDING){
//2.2.7.
return promise2 = new Promise(function(resolve,reject){
//存儲then方法綁定的回調(diào)函數(shù) //2.2.6.
self.onFulfilledCallbacks.push((value)=>{
try{
let x = onFulfilled(value);
resolvePromise(promise2,x,resolve,reject); //2.2.7.1
}catch (e){
reject(e); //2.2.7.2
}
});
self.onRejectedCallbacks.push((reason)=>{
try{
let x= onRejected(reason);
resolvePromise(promise2,x,resolve,reject);
}catch (e){
reject(e);
}
});
});
}
};
關(guān)于platform code
Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.
上面一大段話的意思大致上就是要求 onFulfilled
和 onRejected
回調(diào)函數(shù)確保異步執(zhí)行亡蓉。我們可以選擇用宏任務(wù)(setTimeout/setImmediate)或則微任務(wù)(process.nextTix/MutationObserver)來完成這項(xiàng)規(guī)范晕翠。
這里我們通過在Promise中的resolve和reject方法中套了一個setTimeout()來實(shí)現(xiàn)。
function resolve(value){
setTimeout(function(){ //2.2.4.
if(self.status === PENDING){ //2.2.2.3-2.2.2.4
self.status = FULFILLED;
self.value = value;
self.onFulfilledCallbacks.forEach(cb => cb(self.value)); //2.2.6.
}
})
}
這樣setTimeout中的代碼就會在下一個新的執(zhí)行棧中執(zhí)行砍濒。即使executor中的代碼是同步代碼也一樣淋肾。
let p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('resolve');
})
});
p.then((value)=>{
console.log('fulfilled:',value);
},(reason)=>{
console.log('rejected:',reason);
});
console.log('----------------');
//輸出
>>>----------------
>>>fulfilled: resolve
//----------------------------------------------------------------------------------
let p = new Promise((resolve,reject)=>{
resolve('resolve');
});
p.then((value)=>{
console.log('fulfilled:',value);
},(reason)=>{
console.log('rejected:',reason);
});
console.log('----------------');
//輸出
>>>----------------
>>>fulfilled: resolve
情景:值的穿透
下面的例子中本應(yīng)是第一個then中的參數(shù)會穿透到第二then中作為參數(shù)。
下面兩句再集合resolvePromise方法即是穿透原因
Promise.prototype.then = function (onFulfilled, onRejected) { //2.2.1.
//2.2.7.3-2.2.7.4 //2.2.5.
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; //結(jié)合resolvePromise方法即是穿透原因
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}; //繼續(xù)把異常往后拋
...
//-------------------------------------------------
let p = new Promise((resolve,reject)=>{
resolve('resolve');
});
p.then().then((value)=>{
console.log(value); //會輸出resolve
});
2.3. Promise狀態(tài)解析方法(promise resolution procedure)
let x= onRejected(reason);
resolvePromise(promise2,x,resolve,reject); //resolve/reject為promise2的resolve/reject
Promise狀態(tài)解析方法的作用是將then時返回的promise2的狀態(tài)改變并賦予其vlaue/reason爸邢。
- 如果
x
是一個thenable樊卓,那么該方法將試圖將以x
的狀態(tài)來改變promise2
的狀態(tài) - 否則就將
promise2
改成fulfilled
狀態(tài),并且value即為x
的值
2.3.1. 如果 promise2
和 x
是引用關(guān)系杠河,則拋出一個 TypeError
做為理由來 reject
promise2碌尔。
2.3.2. 如果 x
是一個promise ,讓promise2采用它的狀態(tài)浇辜。
- 如果
x
處于pending,promise2 必須保持pending直到 x 轉(zhuǎn)換為 fulfilled或則rejected七扰。 - 如果
x
是fulfilled
狀態(tài)奢赂,讓promise2也為fulfilled,并且讓promise2的value為x的value颈走。 - 如果
x
是rejected
狀態(tài)膳灶,讓promise2也為rejected,并且讓promise2的value為x的reason立由。
2.3.3. 如果 x
是一個對象或則函數(shù)
- Let
then
bex.then
- 如果檢索
x.then
時候拋出了一個異常e
轧钓,那么以這個e
來rejecte
promise2。 - 如果
then
是一個函數(shù)锐膜,用x
作為this
毕箍,resolvePromise
作為第一個參數(shù),rejectPromise
作為第二個參數(shù)來call
它道盏。- 如果
resolvePromise
被調(diào)用而柑,循環(huán)調(diào)用promise狀態(tài)解析方法
(原本的x替換為調(diào)用resolvePromise傳入的參數(shù),假定為y)荷逞。 - 如果
rejectPromise
被調(diào)用媒咳,則reject Promise2,reason為調(diào)用rejectPromise傳入的參數(shù) - 如果
resolvePromise
和rejectPromise
同時被調(diào)用或則多次調(diào)用种远,那么第一個調(diào)用的擁有優(yōu)先權(quán)涩澡,其它的會被忽略。 - 如果調(diào)用
then
的時候拋出了一個異常e
- 如果
resolvePromise
或rejectPromise
已經(jīng)被調(diào)用坠敷,則忽略它妙同。 - 否則,則用這個
e
來reject
promise2膝迎。
- 如果
- 如果
- 如果then不是一個函數(shù)粥帚,則用
x
來fulfilled
promise2
2.3.4. 如果 x
不是一個函數(shù)也不是一個對象,則用x
來fulfilled
promise2
2.3.3. Promise實(shí)現(xiàn)
resolvePromise方法針對的是then綁定的回調(diào)函數(shù)中的return值進(jìn)行解析限次,一般情況是:
- 當(dāng)return的是普通類型的值茎辐,那么會以這個值來fulfilled promise2
- 如果是一個promise,那么會以這個x promise的結(jié)果來fulfilled/rejected promise2
function resolve(value) {
if(value instanceof Promise){ //和resolvePromise有點(diǎn)聯(lián)系的是 當(dāng)then return的promise中又resolve了一個promise會先走這掂恕,會將resolve里的promise的值賦給調(diào)用resolve的promise(說法欠妥拖陆,意會即可)
return value.then(resolve,reject); //這意味著如果promise1 resolve中是一個promise2,那么promise1狀態(tài)的改變時間會被推遲懊亡,直到promise2狀態(tài)改變調(diào)用promise2的回調(diào)時依啰,promise1狀態(tài)才會改變才會觸發(fā)promise1的回調(diào)
}
...
//---------------------------------------------------------------------------------------------------------
function resolvePromise(promise2,x,resolve,reject){
if(x === promise2){ //2.3.1.
return reject(new TypeError('禁止循環(huán)引用!'));
}
let called =false;
//2.3.2.
if(x instanceof Promise){
if(x.status === PENDING){ //2.3.2.1
x.then((y)=>{
resolvePromise(promise2,y,resolve,reject); //因?yàn)榇藭r的y,有可能也是一個promise //掛上一個鉤子只要x狀態(tài)轉(zhuǎn)化為成功態(tài)就遞歸調(diào)用resolvePromise
},reject);
}else{ //此分支存在的意義在于若executor調(diào)用resolve/reject不是異步的且不在resolve/reject中設(shè)置setTimeout店枣,意味著當(dāng)new的時候就會返回一個帶狀態(tài)的promise就會走這里速警。
x.then(resolve,reject); //2.3.2.2-2.3.2.3 //只要x狀態(tài)改變叹誉,就以x的狀態(tài)和值來改變promise2的狀態(tài)和值 //這個值可能是一個promise,前提是在上面那種假設(shè)實(shí)現(xiàn)中 //如果不符合上面那種實(shí)現(xiàn)且不想像規(guī)范一樣允許值可以為一個promise或則對象 可除去此分支
}
}else if(x!=null&&((typeof x === 'function')||(typeof x === 'object'))){ //2.3.3.
try{
let then = x.then; //2.3.3.1
if(typeof then === 'function'){
//2.3.3.3.
then.call(x,(y)=>{
if(called) return; //2.3.3.3.3.
called = true;
resolvePromise(promise2,y,resolve,reject); //在resolve中又包含promise的情況下闷旧,由于resolve中的 value.then存在长豁,當(dāng)前回調(diào)調(diào)用時,resolve中的promise狀態(tài)一定已經(jīng)改變忙灼,在狀態(tài)已經(jīng)改變的時候利用then綁定回調(diào)匠襟,會走then中的status==fulfilled或則rejected分支
},(reason)=>{
if(called) return;
called = true;
reject(reason);
});
}else{
resolve(x); //2.3.3.4. //1.3
}
}catch (e){
if(called) return; //2.3.3.3.4.1.
called = true;
reject(e); //2.3.3.2. //2.3.3.3.4.2.
}
}else{ //2.3.4.
resolve(x);
}
}
情景:當(dāng)return的是promise且該promise的resolve/reject ()中 也是一個promise
let p = new Promise((resolve,reject)=>{
resolve('resolve1');
});
p.then((value)=>{
return new Promise((resolve,reject)=>{
resolve(new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('別慫')
});
}));
});
}).then((value)=>{
console.log(value); //別慫
});
console.log('----------------');
可見最終的value值為最里層的value值,這樣的實(shí)現(xiàn)關(guān)鍵在于遞歸調(diào)用resolvePromise该园。
...
function resolve(value) {
if(value instanceof Promise){
return value.then(resolve,reject);
...
if(x instanceof Promise){
if(x.status === PENDING){ //2.3.2.1
x.then((y)=>{
resolvePromise(promise2,y,resolve,reject);
},reject);
}else{
x.then(resolve,reject);
}
}
以上這段代碼酸舍,當(dāng)promise1執(zhí)行回調(diào)的時候,會將x傳入resolvePromise執(zhí)行里初,此時由于resolve()方法中的setTimeout啃勉,該x是pending狀態(tài)進(jìn)pending分支,該分支會為X掛上一個鉤子双妨,當(dāng)它狀態(tài)轉(zhuǎn)換后會再次調(diào)用resolvePromise淮阐。
- 如果x的resolve中傳入的也是一個promise (y),由于resolve中添加的value.then刁品,它會推遲x的狀態(tài)轉(zhuǎn)換枝嘶,這意味著X狀態(tài)轉(zhuǎn)換時,y的狀態(tài)一定已經(jīng)轉(zhuǎn)換哑诊,于是會走下面那個分支,調(diào)用y.then及刻,而因?yàn)閥的狀態(tài)已經(jīng)轉(zhuǎn)換镀裤,在then方法中此時就不再能通過狀態(tài)改變時觸發(fā)回調(diào)函數(shù),故要支持此功能需要在then中添加
self.status===FULFILLED/REJECTED
分支缴饭。
}else if(self.status === FULFILLED){
return promise2 = new Promise(function(resolve,reject){
setTimeout(function(){
try{
let x =onFulfilled(self.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
})
});
}else{
return promise2 = new Promise(function(resolve,reject){
setTimeout(function(){
try{
let x =onRejected(self.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
})
});
}
這里用了setTimeout是為了確笔钊埃回調(diào)函數(shù)會異步執(zhí)行。(針對2.2.4.)
- 如果x的resolve傳入的只是一個普通的值颗搂。担猛。。呵呵噠丢氢,那就直接resolve(x)咯
[warning] 值得注意的是: 如果沒有在
resolve()
方法中對value進(jìn)行判斷傅联,那么此時嵌套promise中再嵌套一層promise輸出結(jié)果會是一個promise。因?yàn)榈诙€promise不會等第三個promise狀態(tài)轉(zhuǎn)換后才轉(zhuǎn)換狀態(tài)疚察,這意味著第二個promise的值就為第三個promise對象蒸走。
情景:當(dāng)new的promise中的resolve也是一個promise,而這個promise的resolve中又是一個promise...
此時情況同上個情景貌嫡,得益于then()
中對value的判斷比驻,它會推遲父promise狀態(tài)的轉(zhuǎn)變该溯。
如果沒有這個判斷和推遲,那么也可能最終得到的value是個promise對象别惦。(這是規(guī)范允許的狈茉,但NodeJS和blubird對promise規(guī)范的實(shí)現(xiàn)都對父promise的狀態(tài)轉(zhuǎn)換進(jìn)行了推遲)
情景:在一個已經(jīng)轉(zhuǎn)換了狀態(tài)的promise中再次調(diào)用這個promise的then方法
此時也會走then中的self.status === FULFILLED/REJECTED 的分支,再次證明需要在then中添加這兩個分支并用上settimeout
p1.then((value)=>{ //執(zhí)行此回調(diào)時p1狀態(tài)已經(jīng)改變
p1.then(...);
});
x instanceof Promise 和 typeof x=function... 遞歸的區(qū)別
instance分支下的遞歸 因?yàn)榇嬖趯romise狀態(tài)的判斷掸掸,當(dāng)resolve()
沒有對value進(jìn)行判斷時氯庆,instance分支下的結(jié)果value最終可能為promise對象,而x.then分支下因?yàn)闆]有對promise狀態(tài)進(jìn)行判斷猾漫,故不會出現(xiàn)value為promise對象的情況点晴。
其余Promise方法的實(shí)現(xiàn)
Promise.prototype.catch
此方法實(shí)現(xiàn)灰常簡單,只需在最后一個then綁定完回調(diào)后再綁定一個錯誤的回調(diào)即可
promise.prototype.catch = function(onRejected){
this.then(null,onRejected);
}
Promise.all
此方法傳入一組promise實(shí)例再返回一個最終的promise實(shí)例悯周,當(dāng)所有promise都轉(zhuǎn)為fulfilled時返回的最終的promise實(shí)例將會轉(zhuǎn)換為fulfilled粒督,此時這個promise的值為傳入的promise的值的集合。而如果傳入的那組promise中有一個rejected禽翼,返回的promise就會rejected屠橄。
Promise.all = function(promises){
return new Promise((resolve,reject)=>{
let result = [],
count = 0;
function done(i,data){
result[i] = data;
if(++count===promises.length){
resolve(result);
}
}
for(let i=0;i<promises.length;++i){
promises[i].then((value)=>{
done(i,value);
},(reason)=>{
reject(reason);
});
}
});
}
Promise.race
也是傳入一組promise返回一個promise,哪個promise先轉(zhuǎn)換狀態(tài)闰挡,就返回這個promise的結(jié)果
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;++){
promises[i].then(resolve,reject);
}
});
}
Promise.promisify
將一個異步函數(shù)promise化锐墙,使其可以then,可以鏈?zhǔn)綍鴮?/p>
Promise.promisify = function(fn){
return function(...args){
return new Promise((resolve,reject)=>{
fn.apply(null,[...args,function(err,data){
err?reject(err):resolve(data);
}]);
});
}
}
Promise.promisifyAll
將一個對象下的所有方法都promisify化
Promise.promisifyAll = function(obj){
for(var attr in obj){
if(obj.hasOwnProperty(key)&&typeof obj[attr]==='function'){
obj[attr+'Async'] = Promise.promisify(obj[attr]);
}
}
}
測試
要對實(shí)現(xiàn)的Promise進(jìn)行測試长酗,除了實(shí)現(xiàn)t規(guī)范要求then方法和catch方法外還需要先在你的promise下添加一個方法
Promise.deferred = Promise.defer = function(){
let defer = {};
defer.promise = new Promise(function(resolve,reject){
defer.resolve = resolve;
defer.reject = reject;
});
return defer;
}
然后按下述進(jìn)行測試
npm i -g promises-aplus-tests
promises-aplus-tests yourFileName.js
實(shí)現(xiàn)代碼【終板】
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function Promise(executor) {
let self = this; //緩存下
self.value = undefined; //用來存放value和reason,因?yàn)閜romise只會處于一種狀態(tài)故可只用一個變量來表示溪北。
self.status = PENDING; //將初始狀態(tài)設(shè)置為pending
self.onFulfilledCallbacks = []; //用來存放所有成功的回調(diào)函數(shù)
self.onRejectedCallbacks = []; //用來存放所有失敗的回調(diào)函數(shù)
try {
executor(resolve, reject); //調(diào)用執(zhí)行函數(shù),將resolve和reject方法作為參數(shù)傳入
} catch (e) {
reject(e); //若執(zhí)行函數(shù)中存在異常直接用拋出的值來拒絕promise
}
function resolve(value) {
if (value instanceof Promise) { //和resolvePromise有點(diǎn)聯(lián)系的是 當(dāng)then return的promise中又resolve了一個promise會先走這夺脾,會將resolve里的promise的值賦給調(diào)用resolve的promise(說法欠妥之拨,意會即可)
return value.then(resolve, reject); //這意味著如果promise1 resolve中是一個promise2,那么promise1狀態(tài)的改變時間會被推遲咧叭,直到promise2狀態(tài)改變調(diào)用promise2的回調(diào)時蚀乔,promise1狀態(tài)才會改變才會觸發(fā)promise1的回調(diào)
}
setTimeout(function () {
if (self.status === PENDING) {
self.status = FULFILLED;
self.value = value;
self.onFulfilledCallbacks.forEach(cb => cb(self.value)); //2.2.2. //2.2.6.
}
})
}
function reject(reason) {
setTimeout(function () {
if (self.status === PENDING) {
self.status = REJECTED;
self.value = reason;
self.onRejectedCallbacks.forEach(cb => cb(self.value)); //2.2.3. //2.2.6.
}
})
}
}
Promise.prototype.then = function (onFulfilled, onRejected) { //2.2.1.
//2.2.7.3-2.2.7.4 //2.2.5.
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw reason
};
let self = this,
promise2; //2.2.7.0 //聲明要返回的promise2
if (self.status === PENDING) {
//2.2.7.
return promise2 = new Promise(function (resolve, reject) {
//存儲then方法綁定的回調(diào)函數(shù) //2.2.6.
self.onFulfilledCallbacks.push((value) => {
try {
let x = onFulfilled(value);
resolvePromise(promise2, x, resolve, reject); //2.2.7.1 //resolve/reject屬于promise2 //若此方法執(zhí)行說明promise1狀態(tài)已經(jīng)更改
} catch (e) {
reject(e); //2.2.7.2
}
});
self.onRejectedCallbacks.push((reason) => {
try {
let x = onRejected(reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
} else if (self.status === FULFILLED) {
return promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
});
} else {
return promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRejected(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
});
}
};
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) { //2.3.1.
return reject(new TypeError('禁止循環(huán)引用!'));
}
let called = false;
//2.3.2.
if (x instanceof Promise) {
if (x.status === PENDING) { //2.3.2.1
x.then((y) => {
resolvePromise(promise2, y, resolve, reject); //因?yàn)榇藭r的y,有可能也是一個promise //掛上一個鉤子只要x狀態(tài)轉(zhuǎn)化為成功態(tài)就遞歸調(diào)用resolvePromise
}, reject);
} else { //此分支存在的意義在于若executor調(diào)用resolve/reject不是異步的且不在resolve/reject中設(shè)置setTimeout菲茬,意味著當(dāng)new的時候就會返回一個帶狀態(tài)的promise就會走這里吉挣。
x.then(resolve, reject); //2.3.2.2-2.3.2.3 //只要x狀態(tài)改變,就以x的狀態(tài)和值來改變promise2的狀態(tài)和值 //這個值可能是一個promise婉弹,前提是在上面那種假設(shè)實(shí)現(xiàn)中 //如果不符合上面那種實(shí)現(xiàn)且不想像規(guī)范一樣允許值可以為一個promise或則對象 可除去此分支
}
} else if (x != null && ((typeof x === 'function') || (typeof x === 'object'))) { //2.3.3.
try {
let then = x.then; //2.3.3.1
if (typeof then === 'function') {
//2.3.3.3.
then.call(x, (y) => {
if (called) return; //2.3.3.3.3.
called = true;
resolvePromise(promise2, y, resolve, reject); //在resolve中又包含promise的情況下睬魂,由于resolve中的 value.then存在,當(dāng)前回調(diào)調(diào)用時镀赌,resolve中的promise狀態(tài)一定已經(jīng)改變汉买,在狀態(tài)已經(jīng)改變的時候利用then綁定回調(diào),會走then中的status==fulfilled或則rejected分支
}, (reason) => {
if (called) return;
called = true;
reject(reason);
});
} else {
resolve(x); //2.3.3.4. //1.3
}
} catch (e) {
if (called) return; //2.3.3.3.4.1.
called = true;
reject(e); //2.3.3.2. //2.3.3.3.4.2.
}
} else { //2.3.4.
resolve(x);
}
}
Promise.deferred = Promise.defer = function () {
let defer = {};
defer.promise = new Promise(function (resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
});
return defer;
};
Promise.prototype.catch = function (onRejected) {
this.then(null, onRejected)
};
Promise.resolve = function (value) {
return new Promise((resolve, reject) => {
resolve(value);
})
};
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
})
};
Promise.all = function(promises){
return new Promise((resolve,reject)=>{
let result = [];
let count = 0;
function done(i,data){
result[i] = data;
if(++count === promises.length){
resolve(result);
}
}
for(let i=0;i<promises.length;++i){
promises[i].then((value)=>{
done(i,value);
},reject);
}
})
};
Promise.race = function(promises){
return new Promise(function(resolve,reject){
for(let i=0;i<promises.length;++i){
promises[i].then(resolve,reject);
}
});
};
Promise.promisify = function(fn){
return function(...args){
return new Promise((resolve,reject)=>{
fn.apply(null,[...args,function(err,data){
err?reject(err):resolve(data);
}]);
});
}
};
Promise.promisifyALL = function(obj){
for(var key in obj){
if(obj.hasOwnProperty(key)&&typeof obj[key]=='function'){
obj[key+'Async'] = Promise.promisify(obj[key]);
}
}
};
module.exports = Promise;