飞道的博客

按照 Promise/A+ 手写Promise,通过promises-aplus-tests的全部872个测试用例

452人阅读  评论(0)

链接:https://juejin.cn/post/6910500073314975758

本文主要讲述如何根据 Promises/A+ 规范,一步步手写一个 Promise 的 polyfill,代码中会配上对应的规范解释。

1. 定义需要的常量和工具方法


   
  1. // 1. 定义表示promsie状态的常量
  2. const PENDING_STATE =  "pending";
  3. const FULFILLED_STATE =  "fulfilled";
  4. const REJECTED_STATE =  "rejected";
  5. // 2. 定义可复用的工具方法
  6. const isFunction = function(fun) {
  7.    return typeof fun ===  "function";
  8. };
  9. const isObject = function(value) {
  10.    return value && typeof value ===  "object";
  11. };
  12. 复制代码

2. Promsie 构造函数

Promsie 构造函数内部,主要做 5 件事儿:


   
  1. function Promise(fun) {
  2.    // 1. 基本的判断: 判断 Promsie 构造函数是否是通过 new 调用,以及调用时传入的参数 fn 是否是一个函数;
  3.    // 2. 定义 promise 实例的基本属性;
  4.    // 3. 定义 resolve 方法;
  5.    // 4. 定义 reject 方法;
  6.    // 5. 执行 fun 函数;
  7. }
  8. 复制代码

2.1 基本的判断


   
  1. function Promise(fun) {
  2.    // 1. 基本的判断
  3.    // 1.1 判断是否是通过new调用
  4.    if (!this || this.constructor !== Promise) {
  5.     throw  new TypeError( "Promise must be called with new");
  6.   }
  7.    // 1.2 判断参数fun是否是一个函数
  8.    if (!isFunction(fun)) {
  9.     throw  new TypeError( "Promise constructor's argument must be a function");
  10.   }
  11.    // ...
  12. }
  13. 复制代码

2.2 定义 promise 实例的基本属性


   
  1. function Promise(fun) {
  2.    // ...
  3.    // 2. 定义基本属性
  4.   this.state = PENDING_STATE;  // promise实例的状态
  5.   this.value = void  0// promise的决议值
  6.    // Promises/A+:2.2.6 一个promise实例,可能会调用多次then函数,所以需要一个数组保存then中注册的回调并记录其调用顺序
  7.   this.onFulfilledCallbacks = [];  // 保存完成回调
  8.   this.onRejectedCallbacks = [];  // 保存拒绝回调
  9.    // ...
  10. }
  11. 复制代码

2.3 定义 resolve 方法


   
  1. function Promise(fun) {
  2.    // ...
  3.    // 3. 定义resolve方法
  4.    const resolve = (value) => {
  5.     resolutionProcedure(this, value);
  6.   };
  7.    // 主要执行Promise的决议逻辑
  8.    const resolutionProcedure = function(promise, x) {
  9.      // ...
  10.   };
  11.    // ...
  12. }
  13. 复制代码

2.3.1 Promise 的决议逻辑

Promise 的决议逻辑是 Promise 的一大重点,也是一大难点,把这个逻辑搞清楚了,手写 Promise 就成功一半了。Promise 的决议函数 resolutionProcedure 接收 2 个参数,第一个参数是需要决议的promise实例,第二个参数是决议值,即调用resolve(x)的时候传进去的参数x。Promise 的决议程序主要做了 4 件事:

  1. 判断 x 和 promise 是否指向同一个对象;

  2. 判断 x 是否是一个 promise 实例;

  3. 判断是否是 thenable;

  4. x 为其他 js 基础值,且未决议,则直接决议;


   
  1. function Promise(fun) {
  2.    // ...
  3.    const resolutionProcedure = function(promise, x) {
  4.      // 3.1 判断 x 和 promise 是否指向同一个对象
  5.      // Promises/A+:2.3.1 如果promise和x引用相同的对象,则抛出一个TypeError为原因拒绝promise。
  6.      if (x === promise) {
  7.        return reject( new TypeError( "Promise can not resolved with it seft"));
  8.     }
  9.      // 3.2 判断x是否是promise
  10.      // Promises/A+:2.3.2 如果x是一个promise,则直接采用它的决议值进行决议
  11.      if (x instanceof Promise) {
  12.        return x.then(resolve, reject);
  13.     }
  14.      // 3.3 判断是否是thenable
  15.      // Promises/A+:2.3.3 如果x是一个对象或函数:
  16.      if (isObject(x) || isFunction(x)) {
  17.       let called =  false;
  18.       try {
  19.          /**
  20.          * Promises/A+:
  21.          * 2.3.3.1 Let then be x.then;
  22.          * 2.3.3.2 如果检索属性x.then导致抛出异常error,则以error为原因拒绝promise;
  23.          */
  24.          // 这里要注意:在规范中有规定检索属性x.then导致抛出异常error的情况处理,以及
  25.          // 在插件promises-aplus-tests的用例中,也有检索属性x.then的时候直接抛出异常的情况,
  26.          // 所以,这里的检索then属性,必须写在try的内部,才能捕获异常。
  27.         let then = x.then;
  28.          if (isFunction(then)) {
  29.            /**
  30.            * Promises/A+:
  31.            * 2.3.3.3 如果then是一个函数,则用x调用它;第一个参数是 resolvePromise,第二个参数是 rejectPromise;
  32.            * 2.3.3.3.3 如果同时调用 resolvePromise 和 rejectPromise,或者多次调用同一个参数,则第一个调用具有优先权,后续的调用将被忽略。(所以需要使用 called 进行控制)
  33.            */
  34.           then.call(
  35.             x,
  36.             (y) => {
  37.                if (called) {
  38.                  return;
  39.               }
  40.               called =  true;
  41.                // Promises/A+:2.3.3.3.1 如果使用一个值y调用了resolvePromise,则执行[[Resolve]](promise, y),即我们写的 resolutionProcedure(promise, y);
  42.               resolutionProcedure(promise, y);
  43.             },
  44.             (error) => {
  45.                if (called) {
  46.                  return;
  47.               }
  48.               called =  true;
  49.                // Promises/A+:2.3.3.3.2 如果使用一个reason调用了rejectPromise,则以这个reason直接拒绝promise;
  50.               reject(error);
  51.             }
  52.           );
  53.            return;
  54.         }
  55.       } catch (error) {
  56.          /**
  57.          * Promises/A+:
  58.          * 2.3.3.3.4 如果调用then函数抛出一个异常:
  59.          * 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 被调用,则忽略它。
  60.          * 2.3.3.3.4.2 否则,以error为理由拒绝promise。
  61.          */
  62.          if (called) {
  63.            return;
  64.         }
  65.         called =  true;
  66.         reject(error);
  67.       }
  68.     }
  69.      // 3.4 x为其他js基础值,且未决议,则直接决议
  70.      /**
  71.      * Promises/A+:
  72.      * 2.3.3.4 如果then不是一个函数,则用x完成promise;
  73.      * 2.3.4 如果x不是对象或函数,则用x完成promise;
  74.      * 2.1 Promise的决议状态是不能变的,一旦决议了,就不能再进行决议,所以这里要先判断promise是否已经决议
  75.      */
  76.      if (promise.state === PENDING_STATE) {
  77.       promise.state = FULFILLED_STATE;
  78.       promise.value = x;
  79.        /**
  80.        * Promises/A+:
  81.        * 2.2.2.3 onFulfilled函数不允许执行超过一次,即最多只能执行一次
  82.        *   (决议之后,立即执行保存的回调。因为promise只能决议一次,所以,保存的回调也正好只能执行一次)
  83.        * 2.2.6.1 所有的onFulfilled回调,必须按照注册的顺序执行
  84.        */
  85.       promise.onFulfilledCallbacks.forEach((callback) => callback());
  86.     }
  87.   };
  88.    // ...
  89. }
  90. 复制代码

2.4 定义 reject 方法


   
  1. function Promise(fun) {
  2.    // ...
  3.    // 4. 定义reject方法(reject方法不会解析接收到的值,接收到啥值就直接拿该值作为拒绝的理由)
  4.    const reject = (reason) => {
  5.      if (this.state === PENDING_STATE) {
  6.       this.state = REJECTED_STATE;
  7.       this.value = reason;
  8.        /**
  9.        * Promises/A+:
  10.        * 2.2.3.3 onRejected不允许执行超过一次,即最多只能执行一次。
  11.        *   (决议之后,立即执行保存的回调。因为promise只能决议一次,所以,保存的回调也正好只能执行一次)
  12.        * 2.2.6.2 所有的onRejected回调,必须按照注册的顺序执行
  13.        */
  14.       this.onRejectedCallbacks.forEach((callback) => callback());
  15.     }
  16.   };
  17.    // ...
  18. }
  19. 复制代码

2.5 执行 fun 函数


   
  1. function Promise(fun) {
  2.    // ...
  3.    // 5. 执行fun函数
  4.   try {
  5.     fun(resolve, reject);
  6.   } catch (error) {
  7.      // 这里需要捕获fun函数执行过程中可能出现的错误;如果fun函数执行出错,则直接拒绝promise。
  8.     reject(error);
  9.   }
  10.    // ...
  11. }
  12. 复制代码

至此,Promise 构造函数就算完成了,接下来我们来看 Promise 的另一个重头戏:then

3. Promise.prototype.then

为什么把 then 单独从原型方法中拎出来,主要还是因为他是除了 Promise 决议逻辑之外的另一个重难点,所以想单独讲解。从大的方面来说,then方法中主要做了 2 件事:

  1. 处理 onFulfilled 或者 onRejected 不是函数的情况;

  2. 创建并返回一个新的 promise 实例;

  • 2.1 利用包装函数将 onFulfilled 和 onRejected 添加到事件队列(在此,我们使用setTimeout)

  • 2.2 判断当前的 promise 状态,决定如何处理传入的回到函数:

    • 2.2.1 若为 fulfilled,则执行 onFulfilled;

    • 2.2.2 若为 rejected,则执行 onRejected;

    • 2.2.3 如果 promise 未决议,则将回调保存在 onFulfilledCallbacks 和 onRejectedCallbacks 中,待 promise 决议之后再执行对应回调;

3.1 处理 onFulfilled 或者 onRejected 不是函数的情况


   
  1. Promise.prototype.then = function(onFulfilled, onRejected) {
  2.    // 1. 处理onFulfilled或者onRejected不是函数的情况
  3.    // Promises/A+:2.2.1 onFulfilled 和 onRejected都是可选的,如果他们不是函数,就会被忽略。
  4.    // Promises/A+:2.2.7.3 如果onFulfilled不是函数,而promise1已经是fulfilled,
  5.    // 则promise2必须用promise1的决议值进行决议,所以这里需要添加 (value) => value 直接返回promise1的决议值
  6.   onFulfilled = isFunction(onFulfilled) ? onFulfilled : (value) => value;
  7.    // Promises/A+:2.2.7.4 如果onRejected不是函数,而promise1已经是rejected,
  8.    // 则promise2必须用promise1拒绝的reason进行拒绝,所以这里需要添加 throw error;
  9.   onRejected = isFunction(onRejected)
  10.     ? onRejected
  11.     : (error) => {
  12.         throw error;
  13.       };
  14.    // ...
  15. };
  16. 复制代码

3.2 创建并返回一个新的 promise 实例


   
  1. Promise.prototype.then = function(onFulfilled, onRejected) {
  2.    // ...
  3.    // 2. 创建并返回一个新的 promise 实例;
  4.    // Promises/A+:2.2.7 then函数必须返回一个promise实例;
  5.    return  new Promise((resolve, reject) => {
  6.      // 2.1 利用包装函数将 onFulfilled 和 onRejected 添加到事件队列(在此,我们使用setTimeout)
  7.     let wrapOnFulfilled = () => {
  8.       setTimeout(() => {
  9.         try {
  10.            // Promises/A+:2.2.5 onFulfilled和onRejected都必须作为函数调用(采用默认调用方式,而非call、apply或者属性的方式)
  11.           let x = onFulfilled(this.value);
  12.            // Promises/A+:2.2.7.1 如果onFulfilled或onRejected返回一个合法值x,就执行Promise决议过程,而非拒绝
  13.           resolve(x);
  14.         } catch (error) {
  15.            //Promises/A+:2.2.7.2 如果onFulfilled或onRejected抛出一个error,就利用error作为reson执行拒绝操作
  16.           reject(error);
  17.         }
  18.       },  0);
  19.     };
  20.     let wrapOnRejected = () => {
  21.       setTimeout(() => {
  22.         try {
  23.            // Promises/A+:2.2.5 onFulfilled和onRejected都必须作为函数调用(采用默认调用方式,而非call、apply或者属性的方式)
  24.           let x = onRejected(this.value);
  25.            // Promises/A+:2.2.7.1 如果onFulfilled或onRejected返回一个合法值x,就执行Promise决议过程,而非拒绝
  26.           resolve(x);
  27.         } catch (error) {
  28.            // Promises/A+:2.2.7.2 如果onFulfilled或onRejected抛出一个error,就利用error作为reson执行拒绝操作
  29.           reject(error);
  30.         }
  31.       },  0);
  32.     };
  33.      // 2.2 判断状态
  34.      // Promises/A+:2.2.2 和 2.2.3 onFulfilled 和 onRejected 都只能在promise被决议之后执行
  35.      // 2.2.1 若为fulfilled,则执行onFulfilled
  36.      if (this.state === FULFILLED_STATE) {
  37.       wrapOnFulfilled();
  38.     }  else  if (this.state === REJECTED_STATE) {
  39.        // 2.2.2 若为rejected,则执行onRejected
  40.       wrapOnRejected();
  41.     }  else {
  42.        // 2.2.3 如果promise未决议,则将回调保存在onFulfilledCallbacks和onRejectedCallbacks中,待promise决议之后再执行对应回调;
  43.       this.onFulfilledCallbacks.push(wrapOnFulfilled);
  44.       this.onRejectedCallbacks.push(wrapOnRejected);
  45.     }
  46.   });
  47. };
  48. 复制代码

4. 其他原型方法

4.1 Promise.prototype.catch


   
  1. Promise.prototype.catch = function(callback) {
  2.    return this.then(null, callback);
  3. };
  4. 复制代码

4.2 Promise.prototype.finally


   
  1. // 无论promise成功或失败,finally方法都会执行接收到的回调函数,并返回一个promise实例:
  2. // 1. 如果回调函数执行出错,将以抛出的错误,拒绝新的promise;
  3. // 2. 否则,新返回的promise会沿用旧promise的决议值进行决议。
  4. Promise.prototype.finally = function(callback) {
  5.    return this.then(
  6.     (data) => {
  7.       callback();
  8.        return data;
  9.     },
  10.     (error) => {
  11.       callback();
  12.       throw error;
  13.     }
  14.   );
  15. };
  16. 复制代码

5. 静态方法

5.1 Promise.resolve


   
  1. // 如果Promise.resolve接收到的是一个promise,则会直接返回这个promise;否则,则会进一步执行决议操作。
  2. Promise.resolve = function(value) {
  3.    return value instanceof Promise
  4.     ? value
  5.     :  new Promise((resolve) => resolve(value));
  6. };
  7. 复制代码

5.2 Promise.reject


   
  1. // Promise.reject无论接收到什么,都会直接以接收到的值作为拒绝理由,而不会像resolve一样进行拆解。
  2. Promise.reject = function(reason) {
  3.    return  new Promise((resolve, reject) => reject(reason));
  4. };
  5. 复制代码

5.3 Promise.race


   
  1. // 需要注意的是,如果Promise.race接收到的是一个空数组([]),则会一直挂起,而不是立即决议。
  2. Promise.race = function(promises) {
  3.    return  new Promise((resolve, reject) => {
  4.     promises.forEach((promise) => {
  5.       Promise.resolve(promise).then(resolve, reject);
  6.     });
  7.   });
  8. };
  9. 复制代码

5.4 Promise.all


   
  1. Promise.all = function(promises) {
  2.    return  new Promise((resolve, reject) => {
  3.      // 如果Promise.all接收到的是一个空数组([]),它会立即决议。
  4.      if (!promises.length) {
  5.       resolve([]);
  6.     }
  7.     let result = [];
  8.     let resolvedPro =  0;
  9.      for (let index =  0, length = promises.length; index < length; index++) {
  10.       Promise.resolve(promises[index]).then(
  11.         (data) => {
  12.            // 注意,这里要用index赋值,而不是push。因为要保持返回值和接收到的promise的位置一致性。
  13.           result[index] = data;
  14.            if (++resolvedPro === length) {
  15.             resolve(result);
  16.           }
  17.         },
  18.         (error) => {
  19.           reject(error);
  20.         }
  21.       );
  22.     }
  23.   });
  24. };
  25. 复制代码

5.5 Promise.allSettled


   
  1. // Promise.allSettled 返回一个在所有给定的promise都已经fulfilled或rejected后的promise,
  2. // 并带有一个对象数组,每个对象表示对应的promise结果。
  3. Promise.allSettled = function(promises) {
  4.    return  new Promise((resolve, reject) => {
  5.      if (!promises.length) {
  6.       resolve([]);
  7.     }
  8.     let result = [];
  9.     let resolvedPro =  0;
  10.      for (let index =  0, length = promises.length; index < length; index++) {
  11.       Promise.resolve(promises[index])
  12.         .then((data) => {
  13.            // 注意,这里要用index赋值,而不是push。因为要保持返回值和接收到的promise的位置一致性。
  14.           result[index] = {
  15.             status: FULFILLED_STATE,
  16.             value: data,
  17.           };
  18.            if (++resolvedPro === length) {
  19.             resolve(result);
  20.           }
  21.         })
  22.         .catch((error) => {
  23.           result[index] = {
  24.             status: REJECTED_STATE,
  25.             reason: error,
  26.           };
  27.            if (++resolvedPro === length) {
  28.             resolve(result);
  29.           }
  30.         });
  31.     }
  32.   });
  33. };
  34. 复制代码

至此,手写一个 Promise 就算完成了,此 Promise 完全按照 Promise/A+ 的要求写的,也全部通过了promises-aplus-tests的 872 个测试用例。如果您还发现有什么欠缺的地方,欢迎指正;如果觉得对您有用,也请点个赞哦。代码 github 地址: [github.com/ydiguo/Prom…] Promises/A+ 规范(译本): [juejin.cn/post/691047…]

前端学习笔记????

最近花了点时间把笔记整理到语雀上了,方便同学们阅读:公众号回复笔记或者简历

最后

1.看到这里了就点个在看支持下吧,你的「点赞,在看」是我创作的动力。

2.关注公众号前端壹栈,回复「1」加入前端交流群!「在这里有好多前端开发者,会讨论前端知识,互相学习」!

3.也可添加公众号【前端壹栈】,一起成长


转载:https://blog.csdn.net/qq_34998786/article/details/111829456
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场