Promise/A+ 規範的實現

百命發表於2018-01-19

官方文件地址 https://promisesaplus.com/ 本文不涉及具體用法,只從具體實現上出發

第一步,實現同步版的promise

promise包含幾個關鍵詞:

  • resolve
  • reject
  • then

其中resolve和reject的程式碼在正常使用的時候是看不到的,但可以猜測他們兩個都應該是回撥函式,傳遞給了使用者傳入的函式,而then則掛在原型上

結構如下:

calss Promise{
    consturctor(exector){
        function resolve(){
            
        }
        function reject(){

        }
        exector(resolve,reject)
    }
    then(){

    }
}
複製程式碼

文件中提出,promise具備三種邏輯判斷狀態pending、fulfilled、rejected,三者不併處,同一時間只能存在一種狀態

pending向fulfilled或rejected單向流動。

同步版本實現

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class Promise{
    constructor(exector){
        let self = this;
        self.status = PENDING;
        self.value = undefined;
        self.reason = undefined;
        let resolve = (value)=>{
            if(self.status === PENDING){
                self.status = FULFILLED
                self.value = value;
            }
        }
        let reject = (reason)=>{
            if(self.status === PENDING){
                self.status = REJECTED;
                self.reason = reason
            }
        }
        try{
            exector(resolve,reject)
        }catch(e){
            reject(e)
        }
    }
    then(onFulfilled,onRejected){
        let self = this;
        if(self.status === FULFILLED){
            onFulfilled(self.value);
        }
        if(self.status === REJECTED){
            onRejected(self.reason);
        }
    }
};
複製程式碼

現在測試一下:

new Promise((resolve,reject)=>{
    reject("1");
}).then((data)=>{
    console.log(data)
},(reason)=>{
    console.log(reason)
})
複製程式碼

resolve和reject都能夠得到準確輸出

存在的問題

非同步不支援

new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(2)
    })
}).then((data)=>{
    console.log(data)
},(reason)=>{
    console.log(reason)
})
複製程式碼

現在用setTimeout包裹resolve,結果就是什麼輸出也沒有

同步的話,resolve先執行,onFulfilled後執行,此時狀態已經變成了fulfilled

非同步的話,then先執行,此時status還是pending,無法進入fulfilled狀態,所以onFulfilled不會執行,setTimeout之後resolve改變status,但已經找不到onFulfilled了

then沒有容錯

new Promise((resolve,reject)=>{
    resolve(2)
}).then()
複製程式碼

像上面這種情況,如果沒有給then方法傳遞引數,那麼程式會報錯

解決存在的問題

非同步promise

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class Promise{
    constructor(exector){
        let self = this;
        self.status = PENDING;
        self.value = undefined;
        self.reason = undefined;
        self.onResolveCallBacks = [];
        self.onRejectCallBacks = [];
        let resolve = (value)=>{
            if(self.status === PENDING){
                self.status = FULFILLED
                self.value = value;
                self.onResolveCallBacks.forEach(cb=>cb(self.value));
            }
        }
        let reject = (reason)=>{
            if(self.status === PENDING){
                self.status = REJECTED;
                self.reason = reason
                self.onRejectCallBacks.forEach(cb=>cb(self.reason));
            }
        }
        try{
            exector(resolve,reject)
        }catch(e){
            reject(e)
        }
    }
    then(onFulfilled,onRejected){
        let self = this;
        if(self.status === FULFILLED){
            onFulfilled(self.value);
        }
        if(self.status === REJECTED){
            onRejected(self.reason);
        }
        if(self.status === PENDING){
            self.onResolveCallBacks.push(onFulfilled);
            self.onRejectCallBacks.push(onRejected);
        }
    }
};
複製程式碼

因為resolve和then的執行順序無法保證,所以要用訂閱釋出的方式來實現,所以建立兩個陣列,分別儲存成功和失敗的回撥函式

self.onResolveCallBacks = [];
self.onRejectCallBacks = [];
複製程式碼

然後再改寫resolve和reject函式,執行釋出

let resolve = (value)=>{
    if(self.status === PENDING){
        self.status = FULFILLED
        self.value = value;
        self.onResolveCallBacks.forEach(cb=>cb(self.value));
    }
}
複製程式碼

此時進行測試:

new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject("失敗")
    },2000)
}).then((data)=>{
    console.log(data);
},(reason)=>{
    console.log(reason);
})
複製程式碼

成功輸出失敗,到此非同步的promise完成了

then容錯

總錯這裡不太好理解,所以後面再做

第二步,Promise的鏈式呼叫

promise支援鏈式呼叫,使用方法如:

new Promise((resolve,reject)=>{
    //resolve or reject
}).then((d)=>{
    return d
},(r)=>{
    return r
}).then((d)=>{
    return d
},(r)=>{
    return r
}).then((data)=>{
    console.log(data);
},(reason)=>{
    console.log(reason);
})
複製程式碼

上面的.then中return後再then就是鏈式呼叫

前面知道promise例項具備then方法,所以如果我們的then方法執行後,再返回一個Promise方法的話,不就可以繼續進行.then了嗎?

同步鏈式呼叫

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class Promise{
    constructor(exector){
        let self = this;
        self.status = PENDING;
        self.value = undefined;
        self.reason = undefined;
        self.onResolveCallBacks = [];
        self.onRejectCallBacks = [];
        let resolve = (value)=>{
            if(self.status === PENDING){
                self.status = FULFILLED
                self.value = value;
                self.onResolveCallBacks.forEach(cb=>cb(self.value));
            }
        }
        let reject = (reason)=>{
            if(self.status === PENDING){
                self.status = REJECTED;
                self.reason = reason
                self.onRejectCallBacks.forEach(cb=>cb(self.reason));
            }
        }
        try{
            exector(resolve,reject)
        }catch(e){
            reject(e)
        }
    }
    then(onFulfilled,onRejected){
        // onFulfilled = typeof onFulfilled == 'function'?onFulfilled:value=>value;
        // onRejected = typeof onRejected == 'function'?onRejected:reason => {throw reason};
        let self = this;
        if(self.status === FULFILLED){
            return new Promise((resolve,reject)=>{
                let x = onFulfilled(self.value);
                resolve(x);
            })
        }
        if(self.status === REJECTED){
            return new Promise((resolve,reject)=>{   
            let x = onRejected(self.reason);
                reject(x);
            })
        }
        if(self.status === PENDING){
            self.onResolveCallBacks.push(onFulfilled);
            self.onRejectCallBacks.push(onRejected);
        }
    }
};
複製程式碼

這裡核心就是then方法返回了一個新的promise例項:

return new Promise((resolve,reject)=>{
    let x = onFulfilled(self.value);
    resolve(x);
})
複製程式碼

1,pormise(p1)執行resolve,改變status為fulfilled

2,then方法執行onFulfilled

3,onFulfilled返回一個新的promise,簡稱p2

4,由於p1是resolve,所以p2也知行resolve

5,p2執行,改變了p2的status為fulfilled

6,p2繼續執行p2的onFulfilled

至此就做到了一個同步的鏈式呼叫

非同步鏈式呼叫

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class Promise{
    constructor(exector){
        let self = this;
        self.status = PENDING;
        self.value = undefined;
        self.reason = undefined;
        self.onResolveCallBacks = [];
        self.onRejectCallBacks = [];
        let resolve = (value)=>{
            if(self.status === PENDING){
                self.status = FULFILLED
                self.value = value;
                self.onResolveCallBacks.forEach(cb=>cb(self.value));
            }
        }
        let reject = (reason)=>{
            if(self.status === PENDING){
                self.status = REJECTED;
                self.reason = reason
                self.onRejectCallBacks.forEach(cb=>cb(self.reason));
            }
        }
        try{
            exector(resolve,reject)
        }catch(e){
            reject(e)
        }
    }
    then(onFulfilled,onRejected){
        let self = this;
        if(self.status === FULFILLED){
            return new Promise((resolve,reject)=>{
                let x = onFulfilled(self.value);
                resolve(x);
            })
        }
        if(self.status === REJECTED){
            return new Promise((resolve,reject)=>{   
            let x = onRejected(self.reason);
                reject(x);
            })
        }
        if(self.status === PENDING){
            return new Promise(function(resolve,reject){
                self.onResolveCallBacks.push(function(){
                    let x = onFulfilled(self.value);
                    resolve(x);
                });
                self.onRejectCallBacks.push(function(){
                    let x = onRejected(self.reason);
                    reject(x);
                });
            })
        }
    }
};

複製程式碼

這裡的核心是當status為pending狀態時,同樣要立刻返回一個promise物件,否則沒有返回值的話,第二次鏈式呼叫的then方法根本不存在,需要仔細理解這裡

if(self.status === PENDING){
    return new Promise(function(resolve,reject){
        self.onResolveCallBacks.push(function(){
            let x = onFulfilled(self.value);
            resolve(x);
        });
        self.onRejectCallBacks.push(function(){
            let x = onRejected(self.reason);
            reject(x);
        });
    })
}
複製程式碼

第三步,鏈式呼叫其他的promise實現

經過前面,我們已經實現了一個鏈式呼叫的promise,但還存在一種情況,就是如果第一個onFulfilled返回一個Promise對像的話怎麼處理

new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(new Promise((resolve,reject)=>{
            resolve("10000");
        }))
    },3000)
}).then((d1)=>{
    console.log(d1);
    return d1
},(r1)=>{
    console.log(r1);
    return r1;
}).then((d2)=>{
    console.log(d2);
},(r2)=>{
    console.log(r2);
})
複製程式碼

例如上面的情況,拿到的結果就出現了問題

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class Promise{
    constructor(exector){
        let self = this;
        self.status = PENDING;
        self.value = undefined;
        self.reason = undefined;
        self.onResolveCallBacks = [];
        self.onRejectCallBacks = [];
        let resolve = (value)=>{
            if(self.status === PENDING){
                self.status = FULFILLED
                self.value = value;
                self.onResolveCallBacks.forEach(cb=>cb(self.value));
            }
        }
        let reject = (reason)=>{
            if(self.status === PENDING){
                self.status = REJECTED;
                self.reason = reason
                self.onRejectCallBacks.forEach(cb=>cb(self.reason));
            }
        }
        try{
            exector(resolve,reject)
        }catch(e){
            reject(e)
        }
    }
    then(onFulfilled,onRejected){
        // onFulfilled = typeof onFulfilled == 'function'?onFulfilled:value=>value;
        // onRejected = typeof onRejected == 'function'?onRejected:reason => {throw reason};
        let self = this;
        if(self.status === FULFILLED){
            return new Promise((resolve,reject)=>{
                let x = onFulfilled(self.value);
                resolve(x);
            })
        }
        if(self.status === REJECTED){
            return new Promise((resolve,reject)=>{   
            let x = onRejected(self.reason);
                reject(x);
            })
        }
        if(self.status === PENDING){
            return new Promise(function(resolve,reject){
                self.onResolveCallBacks.push(function(){
                    let x = onFulfilled(self.value);
                    if(x instanceof Promise){
                        x.then(resolve,reject)
                    }else{
                        resolve(x);
                    }
                });
                self.onRejectCallBacks.push(function(){
                    let x = onRejected(self.reason);
                    if(x instanceof Promise){
                        x.then(resolve,reject);
                    }else{
                        reject(x);
                    }
                });
            })
        }
    }
};
複製程式碼

這一次關鍵的程式碼是

 return new Promise(function(resolve,reject){
    self.onResolveCallBacks.push(function(){
        let x = onFulfilled(self.value);
        if(x instanceof Promise){
            x.then(resolve,reject)
        }else{
            resolve(x);
        }
    });
    self.onRejectCallBacks.push(function(){
        let x = onRejected(self.reason);
        if(x instanceof Promise){
            x.then(resolve,reject);
        }else{
            reject(x);
        }
    });
})
複製程式碼

1,通過instanceof判斷了x是否是promise物件

2,如果x是promise物件,稱為p3

3,將p2的resolve當作p3的onFulfilled

4,當p3resolve的時候,實際之行的是p2的resolve

5,p2的resolve執行的時候,會執行第二次的then方法中新增進來的onFulfilled

繼續完善鏈式呼叫

new Promise((resolve,reject)=>{
    // setTimeout(()=>{
        resolve("成功")
    // },3000)/
}).then((d1)=>{
    console.log(d1);
    return new Promise((resolve,reject)=>{
        resolve(new Promise((resolve,reject)=>{
            resolve("111")
        }));
    })
},(r1)=>{
    console.log(r1);
    return r1;
}).then((d2)=>{
    console.log(d2);
},(r2)=>{
    console.log(r2);
})
複製程式碼

例如上面這種,多層promise物件不斷返回的場景

之前的寫法只能滿足一層需求

所以需要使用遞迴來實現,除此之外還要考慮邊界問題,至於邊界可以對照文件來逐步實現,全部實現程式碼如下:

const PENDING =  'pending';//初始態
const FULFILLED =  'fulfilled';//初始態
const REJECTED =  'rejected';//初始態
function Promise(executor){
  let self = this;//先快取當前promise例項
  self.status = PENDING;//設定狀態
  //定義存放成功的回撥的陣列
  self.onResolvedCallbacks = [];
  //定義存放失敗回撥的陣列
  self.onRejectedCallbacks = [];
  //當呼叫此方法的時候,如果promise狀態為pending,的話可以轉成成功態,如果已經是成功態或者失敗態了,則什麼都不做
  //2.1
  function resolve(value){ //2.1.1
    if(value!=null &&value.then&&typeof value.then == 'function'){
      return value.then(resolve,reject);
    }
    //如果是初始態,則轉成成功態
    //為什麼要把它用setTimeout包起來
    setTimeout(function(){
      if(self.status == PENDING){
        self.status = FULFILLED;
        self.value = value;//成功後會得到一個值,這個值不能改
        //呼叫所有成功的回撥
        self.onResolvedCallbacks.forEach(cb=>cb(self.value));
      }
    })

  }
  function reject(reason){ //2.1.2
    setTimeout(function(){
      //如果是初始態,則轉成失敗態
      if(self.status == PENDING){
        self.status = REJECTED;
        self.value = reason;//失敗的原因給了value
        self.onRejectedCallbacks.forEach(cb=>cb(self.value));
      }
    });

  }
  try{
    //因為此函式執行可能會異常,所以需要捕獲,如果出錯了,需要用錯誤 物件reject
    executor(resolve,reject);
  }catch(e){
    //如果這函式執行失敗了,則用失敗的原因reject這個promise
    reject(e);
  };
}
function resolvePromise(promise2,x,resolve,reject){
  if(promise2 === x){
    return reject(new TypeError('迴圈引用'));
  }
  let called = false;//promise2是否已經resolve 或reject了
  if(x instanceof Promise){
    if(x.status == PENDING){
      x.then(function(y){
        resolvePromise(promise2,y,resolve,reject);
      },reject);
    }else{
      x.then(resolve,reject);
    }
  //x是一個thenable物件或函式,只要有then方法的物件,
  }else if(x!= null &&((typeof x=='object')||(typeof x == 'function'))){
    //當我們的promise和別的promise進行互動,編寫這段程式碼的時候儘量的考慮相容性,允許別人瞎寫
   try{
     let then = x.then;
     if(typeof then == 'function'){
       //有些promise會同時執行成功和失敗的回撥
       then.call(x,function(y){
         //如果promise2已經成功或失敗了,則不會再處理了
          if(called)return;
          called = true;
          resolvePromise(promise2,y,resolve,reject)
       },function(err){
         if(called)return;
         called = true;
         reject(err);
       });
     }else{
       //到此的話x不是一個thenable物件,那直接把它當成值resolve promise2就可以了
       resolve(x);
     }
   }catch(e){
     if(called)return;
     called = true;
     reject(e);
   }

  }else{
    //如果X是一個普通 的值,則用x的值去resolve promise2
    resolve(x);
  }
}
//onFulfilled 是用來接收promise成功的值或者失敗的原因
Promise.prototype.then = function(onFulfilled,onRejected){
  //如果成功和失敗的回撥沒有傳,則表示這個then沒有任何邏輯,只會把值往後拋
  //2.2.1
  onFulfilled = typeof onFulfilled == 'function'?onFulfilled:function(value){return  value};
  onRejected = typeof onRejected == 'function'?onRejected:reason=>{throw reason};
  //如果當前promise狀態已經是成功態了,onFulfilled直接取值
  let self = this;
  let promise2;
  if(self.status == FULFILLED){
    return promise2 = new Promise(function(resolve,reject){
      setTimeout(function(){
        try{
          let x =onFulfilled(self.value);
          //如果獲取到了返回值x,會走解析promise的過程
          resolvePromise(promise2,x,resolve,reject);
        }catch(e){
          //如果執行成功的回撥過程中出錯了,用錯誤原因把promise2 reject
          reject(e);
        }
      })

    });
  }
  if(self.status == REJECTED){
    return promise2 = new Promise(function(resolve,reject){
      setTimeout(function(){
        try{
          let x =onRejected(self.value);
          resolvePromise(promise2,x,resolve,reject);
        }catch(e){
          reject(e);
        }
      })
    });
  }
  if(self.status == PENDING){
   return promise2 = new Promise(function(resolve,reject){
     self.onResolvedCallbacks.push(function(){
         try{
           let x =onFulfilled(self.value);
           //如果獲取到了返回值x,會走解析promise的過程
           resolvePromise(promise2,x,resolve,reject);
         }catch(e){
           reject(e);
         }

     });
     self.onRejectedCallbacks.push(function(){
         try{
           let x =onRejected(self.value);
           resolvePromise(promise2,x,resolve,reject);
         }catch(e){
           reject(e);
         }
     });
   });
  }
}
module.exports = Promise;
複製程式碼

主要是通過resolvePromise方法將具體的遞迴,以及邊界問題全部處理完畢

第四步,promise api實現

Promise.all

接收一個陣列,全部成功後才返回

Promise.all = function(arr){
    return new Promise((resolve,reject)=>{
        let resolvList=[];
        arr.forEach((item)=>{
            item.then((data)=>{
                resolvList.push(data);
                console.log(data);
                if(arr.length == resolvList.length){
                    resolve(resolvList);
                }
            },(reason)=>{
                reject(reason);
            })
        })
    })
}
複製程式碼

Promise.race

接受一個陣列,一個成功即返回

Promise.race = function(arr){
    return new Promise((resolve,reject)=>{
        arr.forEach((item)=>{
            item.then((data)=>{
                resolve(data);
            },(reason)=>{
                reject(reason);
            })
        })
    })
}
複製程式碼

Promise.resolve

立刻返回一個promise物件,一般用於沒有promise物件,需要將一個東西,轉為promise

Promise.resolve = function(value){
    return new Promise(function(resolve){
      resolve(value);
    });
  }
複製程式碼

Promise.reject

立刻返回一個promise物件,一般用於沒有promise物件,需要將一個東西,轉為promise

Promise.reject = function(reason){
  return new Promise(function(resolve,reject){
    reject(reason);
  });
}
複製程式碼

終於把這篇寫個大概了,待過一段時間再回顧一遍這個知識,加油

相關文章