帶你實現一個Promise

前言

此篇文章主要是用來加深本身對promise原理的理解。es6

什麼是Promise

Promise是JS解決異步編程的一種方式,是ES6推廣的一個新概念。所謂的Promise至關於一個狀態機,裏面承載着將來某一個時刻所發生的狀態。編程

Promise的狀態

Promise的有三種狀態:數組

  • pending: 一個promise在resolve或者reject前就處於這個狀態。
  • fulfilled: 一個promise被resolve後就處於fulfilled狀態,這個狀態不能再改變,並且必須擁有一個不可變的值(value)。
  • rejected: 一個promise被reject後就處於rejected狀態,這個狀態也不能再改變,並且必須擁有一個不可變的拒絕緣由(reason)。

Promise的特色是什麼

  • 對象的狀態不受外界的影響,只有異步結果才能決定對象的狀態
  • 對象的狀態一旦發生改變,就不會再改變了。任什麼時候刻均可以拿到此結果

Promise解決了什麼問題

Promise和回調函數是2個概念,Promise是新的概念而不是回調函數的擴展。promise

  • 使用Promise解決了回調地獄的問題markdown

    Promise經過鏈式調用解決了回調函數層層嵌套的問題,異步

  • 使用Promise處理異步編程,不會使得代碼邏輯跳躍異步編程

    最原始的回調函數處理異步編程使得代碼跳躍,由於當異步有告終果纔會去執行回調函數,那麼callback的執行和定義可能並不在同一個地方。使得代碼的可讀性沒那麼清晰。而Promsie經過.then函數調用就能夠解決這樣的邏輯跳躍問題。函數

  • 使用Promsie能夠捕獲處理錯誤信息oop

    最原始的回調函數處理異步編程沒法實現錯誤信息的捕獲,那麼經過使用Promise的.catch方法或者在.then的第二個參數(回調函數)來處理錯誤信息。測試

Promsie實現思路分析

在實現Promsie的過程中,須要先了解Promises/A+規範,這裏可查看Promises/A+規範翻譯

Promise的基本結構

let p=new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('FULFILLED');
  }, 1000)
}).then(res=>{
	console.log(res);
}).catch(err=>{
	console.log(err);
})
複製代碼

如上是使用ES6中Promise的基本結構,可見Promise是一個構造函數或者是一個類,傳入一個回調函數,回調函數接收resolve和reject兩個方法;而且Promise有.then和.catch基本這些基本的方法。

then方法

promise.then(onFulfilled, onRejected)
複製代碼

一個Promise必須擁有then方法,並且then方法接收2個可選參數onFulfilled和onRejected。

  • onFulfilled 用來當狀態值是Fulfilled接受的處理函數。
  • onRejected 用來處理狀態值是Rejected拒絕的處理函數。
  • then 方法能夠被同一個 promise 調用屢次
    • 當 promise 成功執行時,全部 onFulfilled 需按照其註冊順序依次回調
    • 當 promise 被拒絕執行時,全部的 onRejected 需按照其註冊順序依次回調
  • then 方法返回一個 promise 對象。

catch方法

一個Promise必須擁有catch方法,用來處理拒絕的狀況。

promise.catch(onRejected)
複製代碼

Promise的狀態值分析

Promise有三個狀態值,因此先定義三個狀態常量。一個Promise的當前狀態必須是等待狀態(Pending), 接受狀態(Fulfilled) 和 拒絕狀態(Rejected)當中的一種。

// 先定義三個常量表示狀態
  const PENDING = "pending";
  const FULFILLED = "fulfilled";
  const REJECTED = "rejected";
複製代碼
  • 當Promsie的狀態值是pending時,那麼它有可能變成Fulfilled或者是Rejected。
  • 當Promise的狀態是Fulfilled或者是Rejected,那麼就沒法改變它的值了。
  • 當狀態值爲rejected時,有一個不可變的拒絕緣由。

Promise封裝

MyPromise接收一個fn(函數),執行這個fn,而且將resolve和reject傳遞出去。

  • 當調用resolve時將狀態值PENDING變成FULFILLED,而且接收value值(成功的值)
  • 當調用reject時將狀態值PENDING變成REJECTED,而且接收reason值(失敗拒絕的值)

MyPromise經過使用try--catch來捕獲錯誤信息。

function MyPromise(fn) {
    this.status = PENDING; // 初始狀態爲pending
    this.value = null; // 初始化value
    this.reason = null; // 初始化reason
    // 存一下this,以便resolve和reject裏面訪問
    var that = this;
    // resolve方法參數是value
    function resolve(value) {
        if (that.status === PENDING) {
            that.status = FULFILLED;
            that.value = value;
        }
    }

    // reject方法參數是reason
    function reject(reason) {
        if (that.status === PENDING) {
            that.status = REJECTED;
            that.reason = reason;
        }
    }
    try {
        fn(resolve, reject);
    } catch (error) {
        reject(error);
    }
}
複製代碼

接下來實現一個then函數:

根據上面Promises/A+規範的分析可見,then方法返回一個 promise ,而且then方法能夠處理不一樣狀態值的狀況。想一想當咱們使用promise時,若是成功以後就會調用then的onFulfilled方法,當失敗以後就會調用then的onRejected方法。因此,在大致上能肯定的是有條件分支是當狀態值爲FULFILLED和REJECTED這兩種狀況,在想一想狀態值不一樣的時候,是如何調用onFulfilled方法和onRejected方法。

在調用onFulfilled方法和onRejected方法以前會去判斷onFulfilled方法和onRejected方法是否是一個函數,若是是才調用這個函數,不是則直接返回值。(值是promise1的值是根據成功或者失敗決定返回reason仍是value)。其實就是至關於將promise1的值穿透到promise2。

那若是onFulfilled方法和onRejected方法是函數而且有本身的返回值,那麼咱們就要根據Promises/A+規範進行編碼處理。

在將Promises/A+規範轉換成代碼以前,咱們先看看以下的代碼:

new Promise(fn).then(onFulfilled, onRejected);
複製代碼

當咱們以如上代碼使用promise時,Promise的狀態值仍是 PENDING (then方法和Promise構造函數一塊兒執行,根本不知道何時狀態值發生改變)。這時候確定不能當即調onFulfilled或者onRejected的,由於fn到底成功仍是失敗還不知道。那何時知道fn成功仍是失敗呢?答案是fn裏面主動調resolve或者reject的時候。因此要先收集onFulfilled和onRejected方法(確定在then方法裏面去收集);若是狀態值仍是PENDING,應該將onFulfilled和onRejected兩個回調存起來,等到fn有告終論,resolve或者reject的時候再來調用對應的代碼(確定在狀態值發生變化的地方去調用函數)。由於後面then還有鏈式調用,會有多個onFulfilled和onRejected,這裏用兩個數組將他們存起來,等resolve或者reject的時候將數組裏面的所有方法拿出來執行一遍。

這個過程像極了訂閱發佈模式。那這樣的話then方法裏面確定須要多一條狀態值爲PENDING的條件分支。

因此新增MyPromise函數的代碼以下:

function MyPromise(fn) {
    ...
    // 構造函數裏面添加兩個數組存儲成功和失敗的回調
    + this.onFulfilledCallbacks = [];
    + this.onRejectedCallbacks = [];

    // 存一下this,以便resolve和reject裏面訪問
    var that = this;
    // resolve方法參數是value
    function resolve(value) {
        if (that.status === PENDING) {
            ...
            // resolve裏面將全部成功的回調拿出來執行
           + that.onFulfilledCallbacks.forEach((callback) => {
           +    callback(that.value);
           + });
        }
    }

    // reject方法參數是reason
    function reject(reason) {
        if (that.status === PENDING) {
         	...
            // resolve裏面將全部失敗的回調拿出來執行
           + that.onRejectedCallbacks.forEach((callback) => {
           +     callback(that.reason);
           + });
        }
    }
	...
}
複製代碼

回到當onFulfilled方法和onRejected方法是函數而且有本身的返回值時的狀況,這時候就須要關注Promises/A+規範的Promise 解決過程,將 Promise 解決過程轉成代碼以下:

function resolvePromise(promise, x, resolve, reject) {
  // 若是 promise 和 x 指向同一對象,以 TypeError 爲據因拒絕執行 promise
  // 這是爲了防止死循環
  if (promise === x) {
      return reject(
          new TypeError("The promise and the return value are the same")
      );
  }

  if (x instanceof MyPromise) {
      // 若是 x 爲 Promise ,則使 promise 接受 x 的狀態
      // 也就是繼續執行x,若是執行的時候拿到一個y,還要繼續解析y
      // 這個if跟下面判斷then而後拿到執行其實重複了,無關緊要
      x.then(function(y) {
          resolvePromise(promise, y, resolve, reject);
      }, reject);
  }
  // 若是 x 爲對象或者函數
  else if (typeof x === "object" || typeof x === "function") {
      // 這個坑是跑測試的時候發現的,若是x是null,應該直接resolve
      if (x === null) {
          return resolve(x);
      }

      try {
          // 把 x.then 賦值給 then
          var then = x.then;
      } catch (error) {
          // 若是取 x.then 的值時拋出錯誤 e ,則以 e 爲據因拒絕 promise
          return reject(error);
      }

      // 若是 then 是函數
      if (typeof then === "function") {
          var called = false;
          // 將 x 做爲函數的做用域 this 調用之
          // 傳遞兩個回調函數做爲參數,第一個參數叫作 resolvePromise ,第二個參數叫作 rejectPromise
          // 名字重名了,我直接用匿名函數了
          try {
              then.call(
                  x,
                  // 若是 resolvePromise 以值 y 爲參數被調用,則運行 [[Resolve]](promise, y)
                  function(y) {
                      // 若是 resolvePromise 和 rejectPromise 均被調用,
                      // 或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用
                      // 實現這條須要前面加一個變量called
                      if (called) return;
                      called = true;
                      resolvePromise(promise, y, resolve, reject);
                  },
                  // 若是 rejectPromise 以據因 r 爲參數被調用,則以據因 r 拒絕 promise
                  function(r) {
                      if (called) return;
                      called = true;
                      reject(r);
                  }
              );
          } catch (error) {
              // 若是調用 then 方法拋出了異常 e:
              // 若是 resolvePromise 或 rejectPromise 已經被調用,則忽略之
              if (called) return;

              // 不然以 e 爲據因拒絕 promise
              reject(error);
          }
      } else {
          // 若是 then 不是函數,以 x 爲參數執行 promise
          resolve(x);
      }
  } else {
      // 若是 x 不爲對象或者函數,以 x 爲參數執行 promise
      resolve(x);
  }
}
複製代碼

在完成Promise 解決過程以後,咱們還需討論onFulfilled 和 onRejected 方法在如何執行?是同步執行仍是異步執行呢?

在規範中講到:實踐中要確保 onFulfilled 和 onRejected 方法異步執行,且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行。

因此在咱們執行onFulfilled 和 onRejected的時候都應該包到setTimeout裏面去。那麼then 方法的實現代碼以下:

MyPromise.prototype.then = function(onFulfilled, onRejected) {
      // 若是onFulfilled不是函數,給一個默認函數,返回value
      var realOnFulfilled = onFulfilled;
      // 若是onRejected不是函數,給一個默認函數,返回reason的Error
      var realOnRejected = onRejected;
      var that = this; // 保存一下this
      if (this.status === FULFILLED) {
          var promise2 = new MyPromise(function(resolve, reject) {
              setTimeout(function() {
                  try {
                      if (typeof onFulfilled !== "function") {
                          resolve(that.value);
                      } else {
                          var x = realOnFulfilled(that.value);
                          resolvePromise(promise2, x, resolve, reject);
                      }
                  } catch (error) {
                      reject(error);
                  }
              }, 0);
          });
          return promise2;
      }

      if (this.status === REJECTED) {
          var promise2 = new MyPromise(function(resolve, reject) {
              setTimeout(function() {
                  try {
                      if (typeof onRejected !== "function") {
                          reject(that.reason);
                      } else {
                          var x = realOnRejected(that.reason);
                          resolvePromise(promise2, x, resolve, reject);
                      }
                  } catch (error) {
                      reject(error);
                  }
              }, 0);
          });
          return promise2;
      }

      // 若是仍是PENDING狀態,將回調保存下來
      if (this.status === PENDING) {
          var promise2 = new MyPromise(function(resolve, reject) {
              that.onFulfilledCallbacks.push(function() {
                  setTimeout(function() {
                      try {
                          if (typeof onFulfilled !== "function") {
                              resolve(that.value);
                          } else {
                              var x = realOnFulfilled(that.value);
                              resolvePromise(promise2, x, resolve, reject);
                          }
                      } catch (error) {
                          reject(error);
                      }
                  }, 0);
              });
              that.onRejectedCallbacks.push(function() {
                  setTimeout(function() {
                      try {
                          if (typeof onRejected !== "function") {
                              reject(that.reason);
                          } else {
                              var x = realOnRejected(that.reason);
                              resolvePromise(promise2, x, resolve, reject);
                          }
                      } catch (error) {
                          reject(error);
                      }
                  }, 0);
              });
          });

          return promise2;
      }
};
複製代碼

其餘Promise方法

根據ES6的文檔Promise 對象的用法手寫Promise的其餘方法,這裏先拎出來各方法的特色再編寫代碼。

1.MyPromise.resolve特色

  • 參數是一個 Promise 實例則返回該實例
  • 參數不是Promise 實例則包裝成Promise對象而且直接resolve參數
MyPromise.resolve = function(parameter) {
    if (parameter instanceof MyPromise) {
        return parameter;
    }

    return new MyPromise(function(resolve) {
        resolve(parameter);
    });
};
複製代碼
  1. MyPromise.reject特色
  • 返回一個新的 Promise 實例,該實例的狀態爲reject,而且據由於傳入的參數
MyPromise.reject = function(reason) {
    return new MyPromise(function(resolve, reject) {
        reject(reason);
    });
};

複製代碼

3.MyPromise.all特色

  • 傳入一個數組做爲參數,數組的每個元素都是一個promise實例
  • 返回一個新的promise,而且promise的結果值是一個數組,數組中的值爲參數中每個promise的返回值而且一一對應的關係
  • 只要參數中的promise實例一個發生錯誤了,那麼這個新的promise狀態值就是拒絕狀態
MyPromise.all = function(args=[]) {
    let count=0;
    let callbackArr=[];
    return new Promise((resolve,reject)=>{
      for(let i=0;i<args.length;i++){
       // (function(i){
          args[i].then(res=>{
            callbackArr[i]=res;
            count++;
            if(count==args.length){
              return resolve(callbackArr);
            }
          }).catch(err=>{
            return reject(err);
          })
        //})(i)
      }
    })
};

複製代碼

MyPromise.prototype.catch 特色

  • .catch()方法是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。
MyPromise.prototype.catch = function(onRejected) {
     this.then(null, onRejected);
 };

複製代碼

其餘的方法小夥伴自行實現,抓住特色,我以爲實現其餘就不會很難了。

總結

知原理知天下,當會使用一個語法的時候多考慮背後的實現思想原理,會加深對此語法的理解。

相關文章
相關標籤/搜索