背景
Promise是非同步程式設計的一種解決方案,它可以解決非同步回撥地獄的問題,防止層層巢狀對程式程式碼帶來的難維護性。既然帶來了方便,我們就有必要學習它的原理以及底層實現,所以筆者就按照PromiseA+規範寫了一個簡單的Promise,並實現了Promise.all(),Promise.race()等API
實現過程
1.定義Promise,並傳入一個需要執行的task函式,以及Promise中非常重要的三種狀態
//定義Promise的三種狀態
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function Promise(executor){}
複製程式碼
2.設定預設狀態,並定義成功和失敗的回撥函式陣列(為了解決鏈式呼叫的問題)
//設定預設狀態
self.status = PENDING;
//存放成功的回撥函式的陣列
self.onResolvedCallbacks = [];
//定義存放失敗回撥函式的陣列
self.onRejectedCallbacks = [];
複製程式碼
3.定義成功和失敗的回撥函式實現
function resolve(value){
if(value!=null &&value.then&&typeof value.then == 'function'){
return value.then(resolve,reject);
}
// 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
setTimeout(function(){
if(self.status == PENDING){
self.status = FULFILLED;
self.value = value;
self.onResolvedCallbacks.forEach(cb=>cb(self.value));
}
})
}
// When rejected, a promise:
// must not transition to any other state.
// must have a reason, which must not change.
function reject(reason){
setTimeout(function(){
if(self.status == PENDING){
self.status = REJECTED;
self.value = reason;
self.onRejectedCallbacks.forEach(cb=>cb(self.value));
}
});
}
複製程式碼
4.實現then方法,這個很重要,就是非同步任務執行成功呼叫then方法,依次走下去,避免了回撥黑洞,其中resolvePromise嚴格按照PromiseA+規範第2.3條去實現
Promise.prototype.then = function(onFulfilled,onRejected){
onFulfilled = typeof onFulfilled == 'function'?onFulfilled:function(value){return value};
onRejected = typeof onRejected == 'function'?onRejected:reason=>{throw reason};
let self = this;
let promise2;
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);
}
})
});
}
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);
}
});
});
}
}
function resolvePromise(promise2,x,resolve,reject){
if(promise2 === x){
return reject(new TypeError('構成迴圈引用'));
}
//promise2是否已經resolve 或reject了
let called = false;
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'))){
try{
let then = x.then;
if(typeof then == 'function'){
then.call(x,function(y){
if(called)return;
called = true;
resolvePromise(promise2,y,resolve,reject)
},function(err){
if(called)return;
called = true;
reject(err);
});
}else{
//x不是一個thenable物件
resolve(x);
}
}catch(e){
if(called)return;
called = true;
reject(e);
}
}else{
resolve(x);
}
}
複製程式碼
5.Promise.all方法用於將多個 Promise 例項,包裝成一個新的 Promise 例項。只有所有例項的狀態都變成fulfilled,最後的狀態才會變成fulfilled,此時返回值組成一個陣列,傳遞給最終的回撥函式。
function gen(times,cb){
let result = [],count=0;
return function(i,data){
result[i] = data;
if(++count==times){
cb(result);
}
}
}
Promise.all = function(promises){
return new Promise(function(resolve,reject){
let done = gen(promises.length,resolve);
for(let i=0;i<promises.length;i++){
promises[i].then(function(data){
done(i,data);
},reject);
}
});
}
複製程式碼
6.Promise.race方法同樣是將多個 Promise 例項,包裝成一個新的 Promise 例項。但是隻要多個例項之中有一個例項率先改變狀態,最終的狀態就跟著改變。那個率先改變的 Promise 例項的返回值,就傳遞給最終的回撥函式。
Promise.race = function(promises){
return new Promise(function(resolve,reject){
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject);
}
});
}
複製程式碼
參考連結
1.PromiseA+規範 2.PromiseA+