實現一個Promise(基於Promise/A+規範)

光光同學發表於2019-01-02

前言

相信大家經常使用Promise,或者使用Generatorasnyc/await等非同步解決方案,網上的Promise原理也遍地開花。
一直以來想抽出時間也寫一寫Promise實現,但是平常工作也是忙的不可開交,正好元旦放了3天假期,休息了2天半,抽出半天時間來看一看Promise

如何使用Promise

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000)
}).then((data) => {
  console.log(data);
  return new Promise((res) => {
    setTimeout(() => {
      res(2);
    },1000)
  })

}).then((res) => {
  console.log(res);
})

複製程式碼

術語

  • Promise是一個包含了相容promise規範then方法的物件或函式。
  • thenable 是一個包含了then方法的物件或函式。
  • value 是任何Javascript值。 (包括 undefined, thenable, Promise等)。
  • exception 是由throw表示式丟擲來的值。
  • reason 是一個用於描述Promise被拒絕原因的值。

由於Promise/A+規範並不包括catchraceall等方法的實現,所以這裡也不會去詳細解釋。

要求

一個Promise必須處在其中之一的狀態:pending, fulfilledrejected。 如果是pending狀態,則promise

可以轉換到fulfilledrejected狀態。 如果是fulfilled狀態,則promise

  • 不能轉換成任何其它狀態。
  • 必須有一個值,且這個值不能被改變。

如果是rejected狀態,則promise可以:

  • 不能轉換成任何其它狀態。
  • 必須有一個原因,且這個值不能被改變。
function MyPromise(callback) {

  let that = this;
  //定義初始狀態
  //Promise狀態
  that.status = 'pending';
  //value
  that.value = 'undefined';
  //reason 是一個用於描述Promise被拒絕原因的值。
  that.reason = 'undefined';

  //定義resolve
  function resolve(value) {
    //當status為pending時,定義Javascript值,定義其狀態為fulfilled
    if(that.status === 'pending') {
      that.value = value;
      that.status = 'resolved';
    }
  }

  //定義reject
  function reject(reason) {
    //當status為pending時,定義reason值,定義其狀態為rejected
    if(that.status === 'pending') {
      that.reason = reason;
      that.status = 'rejected';
    }
  }

  //捕獲callback是否報錯
  try {
    callback(resolve, reject);
  } catch (error) {
    reject(error);
  }
}
複製程式碼

Promise物件有一個then方法,用來註冊在這個Promise狀態確定後的回撥,then方法接受兩個引數: Promise.then(onFulfilled,onRejected)
我們把then函式寫在原型上。

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  let that = this;

  if(that.status === 'resolved') {
    onFulfilled(that.value);
  }

  if(that.status === 'rejected') {
    onRejected(that.reason);
  }
}
複製程式碼

上述程式碼只是實現了Promise的最基本邏輯,如果直接呼叫then是可以執行的,但是並不支援非同步,而Promise最大的特點就是解決callback非同步回撥地獄的問題。
所以我們來改造下。

function MyPromise(callback) {

  let that = this;
  //定義初始狀態
  //Promise狀態
  that.status = 'pending';
  //value
  that.value = 'undefined';
  //reason 是一個用於描述Promise被拒絕原因的值。
  that.reason = 'undefined';
  //用來解決非同步問題的陣列
  that.onFullfilledArray = [];
  that.onRejectedArray = [];

  //定義resolve
  function resolve(value) {
    //當status為pending時,定義Javascript值,定義其狀態為fulfilled
    if(that.status === 'pending') {
      that.value = value;
      that.status = 'resolved';
      that.onFullfilledArray.forEach((func) => {
        func(that.value);
      });
    }
  }

  //定義reject
  function reject(reason) {
    //當status為pending時,定義reason值,定義其狀態為rejected
    if(that.status === 'pending') {
      that.reason = reason;
      that.status = 'rejected';
      that.onRejectedArray.forEach((func) => {
        func(that.reason);
      });
    }
  }

  //捕獲callback是否報錯
  try {
    callback(resolve, reject);
  } catch (error) {
    reject(error);
  }
}
複製程式碼

then函式的改造

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  let that = this;
  //需要修改下,解決非同步問題,即當Promise呼叫resolve之後再呼叫then執行onFulfilled(that.value)。
  //用兩個陣列儲存下onFulfilledArray
  if(that.status === 'pending') {
    that.onFullfilledArray.push((value) => {
      onFulfilled(value);
    });

    that.onRejectedArray.push((reason) => {
      onRejected(reason);
    });
  }

  if(that.status === 'resolved') {
    onFulfilled(that.value);
  }

  if(that.status === 'rejected') {
    onRejected(that.reason);
  }
}
複製程式碼

由於Promise/A+規範規定一個Promise必須處在其中之一的狀態:pending, fulfilledrejected,所以在使用者使用Promise時,寫的是非同步程式碼的話,那麼此時Promise一定是處於pending狀態,反之正常呼叫。
因此,初始化Promise時,定義兩個陣列為onFullfilledArrayonRejectedArray,用來儲存then函式的兩個回撥函式onFulfilledonRejected。同時我們在then函式中判斷status是否是pending,然後將onFulfilledonRejected分別傳入對應陣列中。當使用者呼叫resolvereject時,更改狀態,遍歷陣列,執行onFulfilled或者onRejected,從而非同步呼叫。

then函式的鏈式呼叫

Promise/A+規範中:

對於一個promise,它的then方法可以呼叫多次

  • promise fulfilled後,所有onFulfilled都必須按照其註冊順序執行。
  • promise rejected後,所有OnRejected都必須按照其註冊順序執行。

then 必須返回一個promise

  • 如果onFulfilledonRejected 返回了值x, 則執行Promise 解析流程[[Resolve]](promise2, x)
  • 如果onFulfilledonRejected丟擲了異常e, 則promise2應當以ereason被拒絕。
  • 如果 onFulfilled 不是一個函式且promise1已經fulfilled,則promise2必須以promise1的值fulfilled
  • 如果OnReject 不是一個函式且promise1已經rejected, 則promise2必須以相同的reason被拒絕。
MyPromise.prototype.then = function(onFulfilled, onRejected) {
  let that = this;
  let promise2;

  // 根據標準,如果then的引數不是function,則我們需要忽略它
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(f) {};
  onRejected = typeof onRejected === 'function' ? onRejected : function(r) {};
  //需要修改下,解決非同步問題,即當Promise呼叫resolve之後再呼叫then執行onFulfilled(that.value)。
  //用兩個陣列儲存下onFulfilledArray
  if(that.status === 'pending') {

    return promise2 = new Promise(function(resolve, reject) {
      that.onFullfilledArray.push((value) => {
      try {
        let x = onFulfilled(that.value);
        //判斷onFulfilled是否是一個Promise,如果是,那麼就直接把MyPromise中的resolve和reject傳給then;
        //返回值是一個Promise物件,直接取它的結果做為promise2的結果
        if(x instanceof MyPromise) {
          x.then(resolve, reject);
        }
        //否則,以它的返回值做為promise2的結果
        resolve(x);
      } catch (error) {
        reject(error);
      }
      });

      that.onRejectedArray.push((value) => {
          try {
            let x = onRejected(that.value);
            //判斷onRejected是否是一個Promise,如果是,那麼就直接把MyPromise中的resolve和reject傳給then;
            //返回值是一個Promise物件,直接取它的結果做為promise2的結果
            if(x instanceof MyPromise) {
              x.then(resolve, reject);
            }
            //否則,以它的返回值做為promise2的結果
            resolve(x);
          } catch (error) {
            reject(error);
          }
      });

    })
  }

  if(that.status === 'fulfilled') {
    return promise2 = new MyPromise(function(resolve, reject) {
      try {
        let x = onFulfilled(that.value);
        //判斷onFulfilled是否是一個Promise,如果是,那麼就直接把MyPromise中的resolve和reject傳給then;
        //返回值是一個Promise物件,直接取它的結果做為promise2的結果
        if(x instanceof MyPromise) {
          x.then(resolve, reject);
        }
        //否則,以它的返回值做為promise2的結果
        resolve(x);
      } catch (error) {
        reject(error);
      }
      
    })
  }

  if(that.status === 'rejected') {
    return new MyPromise(function(resolve, reject) {
      try {
        let x = onRejected(that.value);
        //判斷onRejected是否是一個Promise,如果是,那麼就直接把MyPromise中的resolve和reject傳給then;
        //返回值是一個Promise物件,直接取它的結果做為promise2的結果
        if(x instanceof MyPromise) {
          x.then(resolve, reject);
        }
        //否則,以它的返回值做為promise2的結果
        resolve(x);
      } catch (error) {
        reject(error);
      }
      
    })
    
  }
}

複製程式碼

在呼叫then時,判斷onFulfilledonRejected是否是一個函式,如果不是,返回一個匿名函式,同時必須返回各引數的值,用來解決鏈式呼叫時Promise值的穿透問題。
例如:

new MyPromise(resolve=>resolve(8))
  .then()
  .then()
  .then(function foo(value) {
    alert(value)
  })
複製程式碼

所以我們把這塊改成這樣:

  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(f) { return f};
  onRejected = typeof onRejected === 'function' ? onRejected : function(r) {throw r};
複製程式碼

但是這樣每次判斷都需要重新寫這個xMyPromise的關係,所以,我們需要將這塊程式碼給抽象出來,這塊程式碼在Promise/A+規範中叫做resolvePromise

function resolvePromise(promise, x, resolve, reject) {
  let then,thenCalledOrThrow = false
  //如果promise 和 x 指向相同的值, 使用 TypeError做為原因將promise拒絕。
  if (promise === x) {
    return reject(new TypeError('Chaining cycle detected for promise!'))
  }

  //判斷x是否是一個Promise,如果是,那麼就直接把MyPromise中的resolve和reject傳給then;
  //返回值是一個Promise物件,直接取它的結果做為promise2的結果
  if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
    try {
      then = x.then
      if (typeof then === 'function') { // typeof 

        //x.then(resolve, reject);
        then.call(x, function rs(y) {

          if (thenCalledOrThrow) return

          thenCalledOrThrow = true

          return resolvePromise(promise, y, resolve, reject)

        }, function rj(r) {

          if (thenCalledOrThrow) return

          thenCalledOrThrow = true

          return reject(r)

        })
      } else {

        return resolve(x)
      }
    } catch(e) {
      if (thenCalledOrThrow) return

      thenCalledOrThrow = true

      return reject(e)
    }
  } else {

    return resolve(x)
  }

}
複製程式碼

then函式最後修改為:

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  let that = this;
  let promise2;
  // 根據標準,如果then的引數不是function,則我們需要忽略它
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(f) { return f};
  onRejected = typeof onRejected === 'function' ? onRejected : function(r) {throw r};
  //需要修改下,解決非同步問題,即當Promise呼叫resolve之後再呼叫then執行onFulfilled(that.value)。
  //用兩個陣列儲存下onFulfilledArray
  if(that.status === 'pending') {
    return promise2 = new Promise(function(resolve, reject) {
      that.onFullfilledArray.push((value) => {
        try {
          let x = onFulfilled(value);
          resolvePromise(promise2, x, resolve, reject)
        } catch(e) {
          return reject(e)
        }
      });
      
      that.onRejectedArray.push((value) => {
        try {
          let x = onRejected(value);
          resolvePromise(promise2, x, resolve, reject)
        } catch(e) {
          return reject(e)
        }
      });
    })
  }

  if(that.status === 'fulfilled') {
    return promise2 = new MyPromise(function(resolve, reject) {
      try {
        let x = onFulfilled(that.value);
        //處理then的多種情況
        resolvePromise(promise2, x, resolve, reject)
      } catch (error) {
        reject(error);
      }
    })
  }

  if(that.status === 'rejected') {
    return new MyPromise(function(resolve, reject) {
      try {
        let x = onRejected(that.value);
        //處理then的多種情況
        resolvePromise(promise2, x, resolve, reject);
      } catch (error) {
        reject(error)
      }
      
    })
    
  }
}
複製程式碼

測試一下:

new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000)
}).then((data) => {
  console.log(data);
  return new MyPromise((res) => {
    setTimeout(() => {
      res(2);
    },1000)
  })
}).then((res) => {
  console.log(res);
})
//1
//2
複製程式碼

最後

Promise GITHUB地址
參考資料:

《Promise/A+規範》
《Promise3》
《實現一個完美符合Promise/A+規範的Promise》
《剖析Promise內部結構,一步一步實現一個完整的、能通過所有Test case的Promise類》

相關文章