promise的模擬實現

romin發表於2018-03-17

1.概述

1.1 什麼是promise

Promise 物件是 JavaScript 的非同步操作解決方案,為非同步操作提供統一介面。它起到代理作用(proxy),充當非同步操作與回撥函式之間的中介,使得非同步操作具備同步操作的介面。

1.2 promise解決的問題

1.2.1 回撥地獄

Promise 可以讓非同步操作寫起來,就像在寫同步操作的流程,而不必一層層地巢狀回撥函式。

1.2.2 並行結果

如果幾個非同步操作之間並沒有前後順序之分,但需要等多個非同步操作都完成後才能執行後續的任務,無法實現並行節約時間。

1.3 promise簡介

ES6 Promises 標準中定義的API還不是很多,目前大致有下面三種型別。

1.3.1 consturctor

首先,Promise 是一個物件,也是一個建構函式。

function f1(resolve, reject) {
  // 非同步程式碼...
}

var promise = new Promise(f1);
複製程式碼

1.3.2 Instance Method

Promise物件通過自身的狀態,來控制非同步操作。Promise例項具有三種狀態。

  • 非同步操作未完成(pending)
  • 非同步操作成功(fulfilled)
  • 非同步操作失敗(rejected)

這三種的狀態的變化途徑只有兩種。

  • 從“未完成”到“成功”
  • 從“未完成”到“失敗”

所以Promise的最終結果只有兩個,

  • 非同步操作成功,Promise例項傳回一個值(value),狀態變為fulfilled
  • 非同步操作失敗,Promise 例項丟擲一個錯誤(error),狀態變為rejected
promise.then(onFulfilled, onRejected)
// resolve(成功)時,onFulfilled 會被呼叫
// reject(失敗)時,onRejected 會被呼叫
複製程式碼

promise的模擬實現

1.3.3 Static Method

Promise這樣的全域性物件還擁有一些靜態方法。包括 Promise.all()還有Promise.resolve() 等在內,主要都是一些對Promise進行操作的輔助方法。

2.Promise的實現

2.1 resolve和reject

resolvereject都是函式

//建構函式中
function Promise(executor) {
    let self = this;
    
    /*初始化status*/
    self.status = 'pending';
    /*初始化value*/
    self.value = undefined;
    /*訂閱事件的陣列*/
    self.onResolvedCallBacks = [];
    self.onRejectedCallBacks = [];
    
    /*此函式將Promise例項的狀態由pending 轉化為 fulfilled*/
    function resolve(value) {
        if (value instanceof Promise) {
            return value.then(resolve, reject);
        }
        setTimeout(function () {
            if (self.status === 'pending') {
                self.status = 'fulfilled';
                self.value = value;
                /*釋出已經訂閱過的事件*/
                self.onResolvedCallBacks.forEach(item => item(self.value))
            }
        }, 0)
    }
    /*此函式將Promise例項的狀態由pending 轉化為 rejected*/
    function reject(reason) {
        setTimeout(function () {
            if (self.status === 'pending') {
                self.status = 'rejected';
                self.value = reason;
                /*釋出已經訂閱過的事件*/
                self.onRejectedCallBacks.forEach(item => item(self.value))
            }
        }, 0)
    }
    
    // new Promise 的時候,執行器(executor)的程式碼會立即執行
    try {
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }
    
}
複製程式碼

2.2 then方法

promise成功後,onFullfilled會被呼叫。並且把promise的值當做它的第一個引數。promise在成功之前,不會呼叫它,並且只能被呼叫一次。reject也一樣。呼叫 then 方法後,返回一個全新的 promise

// 先封裝一個方法
function resolvePromise(promise2,x,resolve,reject){
//
  if(promise2 === x){
    return reject(new TypeError('迴圈引用'));
  }
  // 為了防止 resolve 和 reject 同時呼叫
  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進行互動,編寫這段程式碼的時候儘量的考慮相容性,
    //允許別人瞎寫,x可以是物件,也可以是函式
    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就可以了
        // 當返回一個物件 {name:'a',then:{age:8}},物件裡的then不是thenable物件
        resolve(x);
      }
    }catch(e){
      if(called)return;
      called = true;
      reject(e);
    }

  }else{
    //如果X是一個普通 的值,則用x的值去resolve promise2
    resolve(x);
  }
}

Promise.prototype.then = function(onFulfilled,onRejected){
  //如果成功和失敗的回撥沒有傳,則表示這個then沒有任何邏輯,只會把值往後拋,叫做值的穿透
  // 例如:promise.then().then().then(function(data){},function(err){})
  
  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);
        }
      });
    });
  }

}
複製程式碼

2.2 catch方法

Promise.prototype.catch=function (callback) {
  return this.then(null,callback);
}
---------使用-------
let promise = new Promise(function (resolve, reject) {
  reject('錯誤');
})
promise.then(function(data){console.log(data)}).catch(e=>{console.log(e)});

複製程式碼

2.3 all 方法

Promise.all = function (promises) {
  return new Promise(function (resolve,reject) {
    let arr =[];//arr是最終返回值的結果的集合。
    let j =0;
    function processData(i,data){// 每呼叫一次此函式,j就會+1;
      arr[i] = data;// 每次成功的結果都放入陣列中
      if(++j===promises.length){
        resolve(arr); //只有promises中的最後一個成功,才能呼叫resolve方法。
      }
    }
    for(let i=0;i<promises.length;i++){
      promises[i].then(function (data) {
          processData(i,data);
      },reject)// 如果有一個失敗,整體就會失敗
    }
  })
}
複製程式碼

使用方法:

function read(file) {
  return new Promise1(function (resolve, reject) {
    require('fs').readFile(file,'utf8',function (err, data) {
      resolve(data);
    })
  })
}

Promise1.all([read('./1.txt'),read('./2.txt')]).then(function (data) {
  console.log(data)
})
// ['file1','file2']
複製程式碼

2.4 race 方法

只要有一個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);
    }
  })
}
複製程式碼

2.5 resolve方法

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

2.6 reject 方法

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

3.Promise物件的缺點:

  • 1、無法取消Promise,一旦新建它就會立即執行,無法中途取消。

  • 2、如果不設定回撥函式,Promise內部丟擲的錯誤,不會反應到外部。

  • 3、當處於Pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

相關文章