實現本身的Promise polyfill

功能清單:

  1. Promise.prototype.then()
  2. Promise.prototype.catch()
  3. Promise.reject()
  4. Promise.resolve()
  5. Promise.all()
  6. Promise.race()
  7. Promise.prototype.finally()

參考:Prmoises A+規範node

具體實現

function Promise(fn) {
  var _this = this; 
  var callback = null;
  this._value = null; // 存放resolve(解決)的結果
  this._reason = null; // 存放reject(拒絕)的緣由
  this._onResolveds = []; // 存放resolve前調用then時傳入的函數onResolved(可能屢次調用then,因此是數組)
  this._onRejecteds = []; // 存放reject前調用catch時傳入的函數onRejected(可能屢次調用catch,因此是數組)
  this._status = "pending";

  function resolve(val) {
    if (_this._status !== 'pending') { // 若status已經改變爲"resolved"或"rejected",回調直接在then內處理
      return
    }
    _this._value = val;
    _this._status = "resolved";
    while (callback = _this._onResolveds.shift()) { // 按註冊順序依次執行回調
      callback(val)
    }
  }

  function reject(reason) {
    if (_this._status !== 'pending') {
      return
    }
    _this._reason = reason;
    _this._status = "rejected";
    while (callback = _this._onRejecteds.shift()) {
      callback(reason)
    }
  }

  try {
    fn(resolve, reject)
  } catch (err) {
    reject(err)
  }
}

then (重點)

Promise.prototype.then = function (onResolved, onRejected) { 
  // 規範:若是 onFulfilled 不是函數,其必須被忽略
  // 這裏,若onResolved, onRejected 不是函數,則用一個過渡性的函數代替 
  onResolved = typeof onResolved === 'function'? onResolved:function(value) {
    return value;  // 將value原封不動地傳給下一個then,至關於跳過(忽略)本輪then的onResolved
  }
  onRejected = typeof onRejected === 'function'? onRejected:function(err) {
    throw err;  // 同上,至關於跳過(忽略)本輪then的onRejected
  }

  var _this = this  
  var promise2  // then始終返回一個Promise實例,用於鏈式調用。

  if (_this._status === "resolved") {
    promise2 = new Promise(function (resolve, reject){
      setTimeout(function() { // 確保onResolved 和 onRejected 方法異步執行。下面的setTimeout同理
        try {
          var x = onResolved(_this._value) 
          resolvePromise(promise2, x, resolve, reject) // resolvePromise內執行promise2的resolve和reject
        } catch (e) {
          reject(e)
        }
      }, 0);
    })
  }
  if (_this._status === "rejected") {
    promise2 = new Promise(function (resolve, reject){
      setTimeout(function() {
        try {
          var x = onRejected(_this._reason)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      }, 0);
    })
  }
  if (_this._status === "pending") {
    // resolve或reject前調用then的話,將回調推入隊列
    promise2 = new Promise(function (resolve, reject) {
      _this._onResolveds.push(function () {
        setTimeout(function() {
          try {
            var x = onResolved(_this._value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0);
      });
      _this._onRejecteds.push(function () {
        setTimeout(function() {
          try {
            var x = onRejected(_this._reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        }, 0);
      });
      
    })
  }

  return promise2
};

根據onResolved/onRejected的返回值 x 的不一樣狀況,調用promise2的resolve和reject

function resolvePromise (promise2, x, resolve, reject) {
  if(promise2 === x) { // 防止引用同一個promise,致使死循環
    return reject(new TypeError('循環引用'));
  }
  var called = false  // 防止屢次調用
  if (x!== null && (typeof x ==='object' ||typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') {  
        // x 是一個定義了 then 方法的對象或函數,即thenable

        then.call(x, function(y) { // 這裏規範是這樣說明的:這步咱們先是存儲了一個指向 x.then 的引用,而後測試並調用該引用,以免屢次訪問 x.then 屬性。這種預防措施確保了該屬性的一致性,由於其值可能在檢索調用時被改變。

          if (called) return   // 確保resolve和reject,只執行其中一個
          called = true;
          resolvePromise(promise2, y, resolve, reject) // 若是x是thenable,則繼續調用resolvePromise,直到 onResolved/onRejected 的返回值不是 thenable

        }, function(err) {
          if (called) return
          called = true;
          reject(err);
        })
      } else {
        resolve(x)  // 若是 x 不屬於 thenable, 則把x做爲返回值.
      }
    } catch (e) {
        if (called) return
        called = true;
        reject(e)
    }
  } else {  //普通值
    resolve(x)
  }
}
Promise.prototype.catch = function (onRejected) {
    return this.then(undefined, onRejected)
  }
Promise.resolve = function (value) {
    return new Promise(function (resolve, reject) {
      resolve(value)
    })
  }
Promise.reject = function (reason) {
    return new Promise(function (resolve, reject) {
      reject(reason)
    })
  }
Promise.all = function (promises) {
    if (!Array.isArray(promises)) {
      throw new TypeError('必須傳入promise數組');
    }
    var length = promises.length
    var values = []

    return new Promise(function (resolve, reject) {
      function rejectHandle(reason) {
        reject(reason) // 只要其中一個reject,總體reject
      }

      function resolveHandle(index) {
        return function (value) {
          values[index] = value // 按傳入時的順序記錄每一個promise的結果值
          if (--length === 0) { // 全部子promise都resolve後,總體resolve
            resolve(values)
          }
        }
      }
      
      promises.forEach(function (item, index) {
        item.then(resolveHandle(index), rejectHandle)
      })
    })
  }
Promise.race = function (promises) {
    if (!Array.isArray(promises)) {
      throw new TypeError('必須傳入promise數組');
    }
    return new Promise(function (resolve, reject) {
      function rejectHandle(reason) {
        reject(reason)
      }

      function resolveHandle(value) {
        resolve(value)
      }
      promises.forEach(function (item) {
        item.then(resolveHandle, rejectHandle)
      })
    })
  }
// 無論resolved仍是rejected,都會執行,避免一樣的語句須要在then()和catch()中各寫一次的狀況。
  Promise.prototype.finally = function (callback) { 
    return this.then(callback, callback)
  }

測試:

使用promises-aplus-tests:全局安裝npm i promises-aplus-tests -g,而後命令行 promises-aplus-tests [js文件名] 進行測試npm

注意:測試前要在尾部加上下面的代碼:數組

Promise.deferred = Promise.defer = function () {
  let deferred = {};
  deferred.promise = new Promise(function (resolve, reject) {
      deferred.resolve = resolve;
      deferred.reject = reject;
  });
  return deferred
};

module.exports = Promise

測試完成後可刪除promise

使用 UMD 規範封裝:

(function (global, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD
    define(factory)
  } else if (typeof exports === 'object' && typeof module !== 'undefined') {
    // CommonJS (如node)
    module.exports = factory()
  } else {
    // 瀏覽器全局變量
    global.promisePolyfill = factory()
  }
})(this, function () {
  'use strict';
   /*
      定義Promise的代碼
   */  

  function promisePolyfill () {
    var global = null
    try {
      global = Function('return this')();
    } catch (e) {
      throw new Error('全局對象不可用');
    }
    global.Promise = Promise
  }
  return promisePolyfill
})

使用

promisePolyfill()  // 註冊Promise全局變量
相關文章
相關標籤/搜索