手写Promise-Promise的解析

0 阅读4分钟

上篇文章中已经完成了Promise的then方法的基本实现。但是还有一个Promise A+中的核心要求没有实现。那就是Promise的解析过程。它是Promise A+规范中最为复杂的部分。因为它要确保不同Promise实现之间的互操作性,因此它的判断的情况也很多。也是理解Promise链式调用的关键。此文将按照Promise A+规范的要求一步一步地实现。

什么是Promise的解析过程?

Promise的解析过程采用递归算法,用于处理then方法返回的值。主要作用:

  1. 处理thenable对象:确保不同Promise实现可以互操作
  2. 防止循环引用:避免Promise链中出现无限循环
  3. 统一值处理:对基本类型、对象、Promise实例进行统一处理

先搭建一个解析Promise的函数

/**
 * Promise的解析过程x
 * @param {Promise} then新创建的promise实例
 * @param {*} then方法回调函数返回的值
 * @param {Function} promise的resolve函数
 * @param {Function} promise的reject函数
 */
function resolvePromise(promise, x, resolve, reject) {
    // 解析过程在这里处理
}

因此上篇文章代码稍微调整一点点

(function () {
  "use strict";
  /* 其他代码省略 */
  definePropertyConfig(prototype, "then", function (onFulfilled, onRejected) {
   /* 其他代码省略 */
   // 将 return  new Promise)() 改为 var promise = new Promise(); return promise
  // 以便 将 promise 传入 handleCallback
    var promise =  new Promise(function (resolve, reject) {
      function handleCallback(value, callback, resolve, reject) {
         setTimeout(function () {
           try {
            var result = callback(value);
            // 在此处进行Promise的解析
            resolvePromise(promise, result, resolve, reject);
           } catch (error) {
             reject(error);
           }
         }, 0);
       }
      if (self.state === FULFILLED) {
        handleCallback(self.result, onFulfilled, resolve, reject);
      } else if (self.state === REJECTED) {
        handleCallback(self.result, onRejected, resolve, reject);
      } else {
        self.onFulfilledCallbacks.push(function (value) {
          handleCallback(value, onFulfilled, resolve, reject);
        });
        
        self.onRejectedCallbacks.push(function (value) {
          handleCallback(value, onRejected, resolve, reject);
        });
      }
    });
    return promise
  });

防止循环引用

Promise A+规范解析过程的第一步: 如果 promise 和 x 引用同一个对象,则以 TypeError 拒绝 promise。为什么需要这样呢?看一段如下代码:

var p = new Promise(function (resolve) {
  resolve();
});

var thenable = {
  then: function (resolve) {
    resolve(thenable); // 这里会造成循环引用
  }
};

let p2 = p.then(function () {
  return p2; // 如果没有检查,将导致无限递归
});

由上述代码可以轻松地编写如下代码

resolvePromise(promose, x, resolve, reject) {
  if (x === promise) {
    throw new TypeError('Chaining cycle detected fro promise #<Promise>');  
  }
}

处理对象和函数的情况

需要判断x是否是一个对象或者函数。

  1. 如果是对象,获取对象属性then,然后判断属性then是否是一个Function类型的
  2. 如果对象属性then是一个函数或者x是一个函数,则调用执行。
  3. 如果不是对象也不是函数的话,直接resolve
  function resolvePromise(promise, x, resolve, reject) {
    if (promise === x) {
      throw new TypeError("Chaining cycle detected for promise #<Promise>");
    }
    if (x !== null && /^(object|function)$/.test(typeof x)) {
      var then = void 0; // 存储then方法
      try {
        then = x.then;
      } catch (error) {
        reject(error);
        return;
      }
      // 判断是否是一个函数,true则直接调用then方法
      if (typeof then === "function") {
        try {
          then.call(
            x,
            function (y) {
              // 递归调用 直到不是thenable
              resolvePromise(promise, y, resolve, reject);
            },
            function (r) {
              reject(r)
            }
          );
        } catch (error) {
          reject(error);
        }
        return;
      }
    }
    // 其他情况直接resolve 
    resolve(x);
  }

添加called标志

为什么需要添加called标志呢?看如下代码:

var dangerousThenable = {
  then: function(resolve, reject) {
    resolve('第一次调用');
    resolve('第二次调用'); // 违反规范:多次调用
    reject('错误调用');    // 违反规范:混合调用
  }
};

为了避免上述情况,代码如下:

function resolvePromise(promise, x, resolve, reject) {
  /* 前面代码省略 */
  if (typeof then === "function") {
    var called = false;
    try {
      then.call(
        x,
        function (y) {
          if (called) return;
          called = true;
          resolvePromise(promise, y, resolve, reject);
        },
        function (r) {
          if (called) return;
          called = true;
          reject(r);
        }
      );
    } catch (error) {
      if (called) return;
      called = true;
        reject(error);
      }
      return;
    }
  }
  resolve(x);
}

测试代码是否符合Promise A+规范

至此Promise的手写告一段落。如何验证我们的代码是否符合Promise A+规范呢?可以通过promises-aplus-tests库来进行验证。

1. 安装promises-aplus-tests

npm i promises-aplus-tests -D

2. 添加测试代码

(function () {
  "use strict";
  /*上面代码省略*/
  
  /*测试代码*/
  Promise.deferred = function () {
    var result = {};
    result.promise = new Promise(function (resolve, reject) {
      result.resolve = resolve;
      result.reject = reject;
    });
    return result;
  };
  // 环境检测与导出
  if (typeof window !== "undefined") {
    window.Promise = Promise;
  }
  if (typeof module === "object" && typeof module.exports === "object") {
    module.exports = Promise;
  }
})();

3. 添加测试运行指令

在package.json中添加运行指令

{
  "script": {
    // 测试文件路径需要根据自己项目确定
    "test": "promises-aplus-tests ./src/index.js"
  }
}

4. 终端输入指令测试

npm run test

输入指令后正常情况,872条测试用例都可以通过。如图所示:

image.png

完整代码示例

(function () {
  "use strict";

  var PENDING = "pending";
  var FULFILLED = "fulfilled";
  var REJECTED = "rejected";

  function Promise(executor) {
    var self = this;

    // 参数校验
    if (typeof executor !== "function") {
      throw new TypeError(`Promise resolver ${executor} is not a function`);
    }
    if (!(self instanceof Promise)) {
      throw new TypeError("Promise constructor must be called with 'new'");
    }

    self.state = PENDING;
    self.result = void 0;
    self.onFulfilledCallbacks = [];
    self.onRejectedCallbacks = [];

    var changeState = function (state, value) {
      if (self.state !== PENDING) return;

      self.state = state;
      self.result = value;

      var callbacks =
        state === FULFILLED
          ? self.onFulfilledCallbacks
          : self.onRejectedCallbacks;

      setTimeout(function () {
        callbacks.forEach(function (callback) {
          typeof callback === "function" && callback(self.result);
        });
      });
    };

    var resolve = function (value) {
      changeState(FULFILLED, value);
    };

    var reject = function (reason) {
      changeState(REJECTED, reason);
    };

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  var prototype = Promise.prototype;

  function definePropertyConfig(object, key, value) {
    Object.defineProperty(object, key, {
      value: value,
      writable: true,
      enumerable: false,
      configurable: true,
    });
  }

  function checkPromiseInstance(value) {
    if (!(value instanceof Promise)) {
      throw new TypeError(
        "Method Promise.prototype.then called on incompatible receiver #<Promise>"
      );
    }
  }
  var resolvePromise = function (promise, x, resolve, reject) {
    if (promise === x) {
      throw new TypeError("Chaining cycle detected for promise #<Promise>");
    }
    if (x !== null && /^(object|function)$/.test(typeof x)) {
      var then = void 0;
      try {
        then = x.then;
      } catch (error) {
        reject(error);
        return;
      }
      if (typeof then === "function") {
        var called = false;
        try {
          then.call(
            x,
            function (y) {
              if (called) return;
              called = true;
              resolvePromise(promise, y, resolve, reject);
            },
            function (r) {
              if (called) return;
              called = true;
              reject(r);
            }
          );
        } catch (error) {
          if (called) return;
          called = true;
          reject(error);
        }
        return;
      }
    }
    resolve(x);
  };

  definePropertyConfig(prototype, "then", function (onFulfilled, onRejected) {
    checkPromiseInstance(this);
    var self = this;
    // 值穿透处理
    onFulfilled =
      typeof onFulfilled === "function"
        ? onFulfilled
        : function (value) {
            return value;
          };
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : function (reason) {
            throw reason;
          };

    function handleCallback(value, callback, resolve, reject) {
      try {
        var result = callback(value);
        resolvePromise(promise, result, resolve, reject);
      } catch (error) {
        console.error(error);
        reject(error);
      }
    }

    var promise = new Promise(function (resolve, reject) {
      if (self.state === FULFILLED) {
        setTimeout(function () {
          handleCallback(self.result, onFulfilled, resolve, reject);
        }, 0);
      } else if (self.state === REJECTED) {
        setTimeout(function () {
          handleCallback(self.result, onRejected, resolve, reject);
        }, 0);
      } else {
        self.onFulfilledCallbacks.push(function (value) {
          handleCallback(value, onFulfilled, resolve, reject);
        });

        self.onRejectedCallbacks.push(function (value) {
          handleCallback(value, onRejected, resolve, reject);
        });
      }
    });
    return promise;
  });
  /*测试代码*/
  Promise.deferred = function () {
    var result = {};
    result.promise = new Promise(function (resolve, reject) {
      result.resolve = resolve;
      result.reject = reject;
    });
    return result;
  };
  // 环境检测与导出
  if (typeof window !== "undefined") {
    window.Promise = Promise;
  }
  if (typeof module === "object" && typeof module.exports === "object") {
    module.exports = Promise;
  }
})();

小结

通过本文的深入探讨,我们完整实现了 Promise A+ 规范中最核心且复杂的解析过程。这不仅是一个技术实现,更是对 JavaScript 异步编程深刻理解的体现。

Promise 的解析过程是异步编程的基石之一,掌握它意味着你对 JavaScript 异步世界的理解达到了新的高度。继续探索,保持好奇,愿你在异步编程的道路上越走越远!

接下来我们将继续完善Promise。如示例方法:catchfinally;静态方法:resolvereject all reace等;

宝剑锋从磨砺出,Promise 妙自解析来 ✨ 期待在一篇文章中与你再见!