zoukankan      html  css  js  c++  java
  • 实现自己的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+规范

    具体实现

    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文件名] 进行测试

    注意:测试前要在尾部加上下面的代码:

    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
    

    测试完成后可删除

    使用 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全局变量
    
  • 相关阅读:
    H5页面跳到安卓APP和iosAPP
    JS location.href传参及接受参数
    获取当前日期及对应星期
    前端获取当前一周时间 数组形式
    Java基础(四) Object 数组转成 String 数组
    定时任务cron表达式详解
    jquery如何删除数组中的一个元素?
    Mybatis Mapper.xml 需要查询返回List<String>
    oracle的 listagg() WITHIN GROUP () 行转列函数的使用
    如何修改Oracle中表的字段长度?
  • 原文地址:https://www.cnblogs.com/JRliu/p/9211611.html
Copyright © 2011-2022 走看看