初識Promise並手寫符合PromiseA+規範的Promise

張淼發表於2018-01-19

背景

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+

相關文章