这次彻底搞懂Promise(手写源码多注释篇)

开发 前端
promise 是 js 里面非常重要的一部分,搞懂了 promise 才能更好的去理解 async,await 和 generator。

[[347964]]

 前言

promise 是 js 里面非常重要的一部分,搞懂了 promise 才能更好的去理解 async,await 和 generator。但是往往很多时候就是不理解 promise 的机制,所以这次通过一步步实现一个 promise 来加深自己的印象,提高自己的思维。

大概的架子

通过我们经常写的 promise 语法,我们可以先写一个大概的架子出来,promise 接受回调,并且调用,自身带有三种状态,pendding, onFulfilled, onRejected,并且 resolve 这个函数可以让 pendding 状态变成 onFulfilled 状态,同理 reject 函数可以让 pendding 状态变成 onRejected 状态。我们先把上面描述部分实现了。 

  1. const PromiseCopy = function (fn) {  
  2.   this.info = {  
  3.     status: "pending",  
  4.     value: "",  
  5.   };  
  6.   const self = this 
  7.   self.onFulfilledArr = []; // then函数里面的第一个回调函数的集合  
  8.   self.onRejectedArr = []; // then函数里面的第二个回调函数的集合  
  9.   const resolve = function (value) {  
  10.     // 加这个判断是为了表示,只有在pendding状态下才会去执行  
  11.     // 状态已经变成onFulfilled之后就不能再去改变了  
  12.     // 符合PromiseA+中的2.1.2.1  
  13.     if (self.info.status === "pending") {  
  14.       self.info.status = "onFulfilled" 
  15.       self.info.value = value;  
  16.       self.onFulfilledArr.forEach((fn) => fn(value));  
  17.     }  
  18.   };  
  19.   // 和上面同理符合PromiseA+,2.1.3.1  
  20.   const reject = function (value) {  
  21.     if (self.info.status === "pending") {  
  22.       self.info.status = "onRejected"  
  23.       self.info.value = value;  
  24.       self.onRejectedArr.forEach((fn) => fn(value));  
  25.     }  
  26.   };  
  27.   fn(resolve, reject);  
  28. }; 

resolve 的附加实现

其实写到这里我们的 resolve 函数还是有一些功能没有实现的, 我们知道 调用 resolve(x), x 的值有好几种情况,如下

  •  如果 x 是 Promise 实例本身,则抛出错误
  •  如果 x 是一个 Promise 对象,那么 then 函数的执行取决这个 x 的状态,如果 x 也调用 resolve(y),其中 y 也是一个 promise 对象.那么 then 函数的执行取决于这个 promise 对象,依次类推,直到最后一个 promise 状态更改
  •  如果 x 是一个 thenable 对象,就是一个对象包含 then 这个属性,或者是一个函数包含一个 then 的静态方法,那么直接执行 then 函数
  •  如果 x 是一个普通值,直接变成 onFulfilled 状态,执行后面的 then 函数

思考

  •  网上实现的大部分 resolve 都是我上面的代码,但是根据规范,resolve 函数里面应该是要判断上面几点的,所以我们上面写的代码是有误的
  •  还有一个问题是,我们需要在 resolve 函数里面判断 x 是不是实例的本身,但是正常的 resolve 函数我们经常是传入一个参数,所以中间肯定是有一个中间函数的,看下面的代码 
  1. const PromiseCopy = function (fn) {  
  2.   this.info = {  
  3.     status: "pending", 
  4.      value: "",  
  5.   };  
  6.   const self = this 
  7.   self.onFulfilledArr = []; // then函数里面的第一个回调函数的集合  
  8.   self.onRejectedArr = []; // then函数里面的第二个回调函数的集合  
  9.   // _resolve 是我们经常调用的resolve  
  10.   // 但是真正实现的应该是里面的resolve  
  11.   const _resolve = function (value) {  
  12.     // 这个函数得改变一下  
  13.     // PromiseCopy一旦被实例化,那么self就是实例本身了  
  14.     resolve(self, value);  
  15.   };  
  16.   // 此时我们就可以在resolve进行判断了  
  17.   const resolve = function (promise, value) {  
  18.     let ifexec = false 
  19.     // 首先判断value是不是promise本身  
  20.     if (value === promise) {  
  21.       // 一定要用TypeError写 不然promises-aplus-tests跑不通  
  22.       // 切记这是第一个坑,promises-aplus-tests只认TypeError这种错误形式  
  23.       reject(new TypeError("A promise cannot be onFulfilled with itself."));  
  24.     }  
  25.     // value是一个thenable对象  
  26.     // 这个要Object.prototype.toString.call(value) === "[object Object]"判断 
  27.  
  28.     // 不然resolve([])有问题,不知道是不是我实现问题  
  29.     if (  
  30.       value &&  
  31.       (Object.prototype.toString.call(value) === "[object Object]" ||  
  32.         typeof value === "function")  
  33.     ) {  
  34.       // var promise1 = Promise.resolve(dump).then(function () {  
  35.       //   return {  
  36.       //     then: (resolve, reject) => {  
  37.       //       setTimeout(() => {  
  38.       //         resolve({  
  39.       //           then: (resolve, reject) => {  
  40.       //             resolve("aa111a");  
  41.       //             throw "other";  
  42.       //           },  
  43.       //         });  
  44.       //       });  
  45.       //     },  
  46.       //   };  
  47.       // });  
  48.       // promise1.then(  
  49.       //   (res) => {  
  50.       //     console.log(res === "aa111a");  
  51.       //     console.log("aaa");  
  52.       //   },  
  53.       //   (res) => {  
  54.       //     console.log(res);  
  55.       //     console.log("error");  
  56.       //   }  
  57.       // );  
  58.       // 这里的try--catch一定要加 ,不然会promises-aplus-tests会一直报错,这是第三个大坑  
  59.       // 因为promises-aplus-test测试里面有这一条的  
  60.       // 看上面注释例子  
  61.       try {  
  62.         // 拿到then函数  
  63.         const then = value.then;  
  64.         // 如果then是一个函数则执行这个函数  
  65.         if (typeof then === "function") {  
  66.           // 为什么要.call(value, x, y) 你们可以自己试一下原生的Promise在这种情况下this指向的就是value,所以要绑定  
  67.           // 因为then我们已经拿出来了then = value.then,直接调用then(),this就指向的window  
  68.           // 为什么后面还需要绑定两个函数了  
  69.           // 根据原生的Promise可知,thenable中的then函数可以接受两个函数resolve,reject  
  70.           // 只有手动调用了resolve和reject才会执行后面的.then操作,具体大家自己操作下  
  71.           then.call(  
  72.             value, 
  73.              function (value) {  
  74.               if (ifexec) {  
  75.                 return;  
  76.               }  
  77.               // ifexec这个一定要加,不然也会报200ms错误,第四个大坑  
  78.               // 目的是为了不让多次执行,语言无法表达看下面的例子  
  79.               // var promise1 = Promise.resolve(dump).then(function () {  
  80.               //   return {  
  81.               //     then: (resolve, reject) => {  
  82.               //       resolve("aa111a");  
  83.               //       resolve("aa111a");  
  84.               //     },  
  85.               //   };  
  86.               // });  
  87.               ifexec = true 
  88.               resolve(promise, value);  
  89.             },  
  90.             function (value) {  
  91.               if (ifexec) {  
  92.                 return;  
  93.               }  
  94.               ifexec = true 
  95.               reject(value);  
  96.             }  
  97.           );  
  98.           return;  
  99.         }  
  100.       } catch (e) {  
  101.         if (ifexec) {  
  102.           return;  
  103.         }  
  104.         ifexec = true 
  105.         reject(e);  
  106.       } 
  107.     }  
  108.     // 下面这一点非常的重要,是async,await 和一些插件比如saga的核心  
  109.     // 就是如果x是一个promise对象,那么then的执行取决于x的状态  
  110.     // 还有这一个判断一定要放在这里,不要和上面的换 不然promises-aplus-tests会报一个超过200ms的错误,切记这是第二个坑  
  111.     if (value && value instanceof PromiseCopy && value.then === promise.then) {  
  112.       // 将promise的onFulfilledArr给到value  
  113.       // 但是还没有那么简单我们要明白两点  
  114.       // 如果value这个promise已经不是pendding,我们给了他也没有用,所以需要直接调用  
  115.       if (value.info.status === "pending") {  
  116.         value.onFulfilledArr = self.onFulfilledArr;  
  117.         value.onRejectedArr = self.onRejectedArr;  
  118.       }  
  119.       // 如果value状态是onFulfilled  
  120.       if (value.info.status === "onRejected") {  
  121.         self.info.value = value.info.value;  
  122.         self.onRejectedArr.forEach((fn) => fn(value.info.value)); 
  123.       }  
  124.       // 如果value状态是reject  
  125.       if (value.info.status === "onFulfilled") {  
  126.         self.info.value = value.info.value;  
  127.         self.onFulfilledArr.forEach((fn) => fn(value.info.value));  
  128.       }  
  129.       return;  
  130.     }  
  131.     // 如果是一个普通的值  
  132.     // 加这个判断是为了表示,只有在pendding状态下才会去执行  
  133.     // 状态已经变成onFulfilled之后就不能再去改变了  
  134.     // 符合PromiseA+中的2.1.2.1  
  135.     if (self.info.status === "pending") {  
  136.       self.info.status = "onFulfilled" 
  137.       self.info.value = value;  
  138.       self.onFulfilledArr.forEach((fn) => fn(value));  
  139.     }  
  140.   };  
  141.   // 和上面同理符合PromiseA+,2.1.3.1  
  142.   // reject没有resolve那么多规则,比较简单  
  143.   const reject = function (value) {  
  144.     if (self.info.status === "pending") {  
  145.       self.info.status = "onRejected" 
  146.       self.info.value = value;  
  147.       self.onRejectedArr.forEach((fn) => fn(value));  
  148.     }  
  149.   };  
  150.   // 此时fn调用的是_reoslve  
  151.   // 这个try catch主要是实现promiseCopy.prototype.catch  
  152.   try {  
  153.     fn(_resolve, reject);  
  154.   } catch (e) {  
  155.     setTimeout(() => {  
  156.       self.onRejectedArr.forEach((fn) => fn(e));  
  157.     });  
  158.   }  
  159. }; 

then 的实现

我们上面介绍的是 promise 的 resolve 用法,promise 还有一个基本用法就是后面接 then,因为是.then 所以我们想到的是这个 then 方法挂在到原型上的,那么 new PromiseCopy 的时候就可以得到这个 then。then 里面是两个函数,一个是 onFulfilled 后执行的回调,一个是 onRejected 后执行的回调。现在的问题是他是怎么做到 then 里面的函数是在 resolve 和 reject 后执行的?这种推迟执行或者说在某种情况下去执行我们想到的就是观察者模式了。下面用代码把上面的话实现一遍,在代码里面会写详细一点的注释。 

  1. PromiseCopy.prototype.then = function (onFulfilled, onRejected) {  
  2.   const self = this 
  3.   // 这里要判断下,如果PromiseCopy是resolve了那么就直接执行onFulfilled  
  4.   if (self.info.status === "onFulfilled") {  
  5.     setTimeout(() => {  
  6.       onFulfilled(self.info.value);  
  7.     });  
  8.   }  
  9.   if (self.info.status === "onRejected") {  
  10.     setTimeout(() => {  
  11.       onRejected(self.info.value);  
  12.     }); 
  13.   }  
  14.   // 根据PromiseA+中的2.2.1.1和2.2.1.2,onFulfilled和onRejected必须是函数,不然就会被忽略  
  15.   if (typeof onFulfilled === "function") {  
  16.     self.onFulfilledArr.push(() => {  
  17.       setTimeout(() => {  
  18.         onFulfilled(self.info.value);  
  19.       });  
  20.     });  
  21.   }  
  22.   if (typeof onRejected === "function") {  
  23.     self.onRejectedArr.push(() => {  
  24.       setTimeout(() => {  
  25.         onRejected(self.info.value);  
  26.       });  
  27.     });  
  28.   }  
  29.   // 根据PromiseA+ 2.2.7规范 then函数必须返回一个promise对象  
  30.   return new PromiseCopy((resolve, reject) => {});  
  31. }; 

then 的额外实现

上面实现的 then 也是一个简单的用法,不过根据 PromiseA+的规范这个 then 函数还有几个点没有实现,看代码解释 

  1. promise2 = promise1.then(onFulfilled, onRejected);  
  2. promise2.then(onFulfilled, onRejected); 
  •  promise1.then 中的 onFulfilled,onRejected 函数如果返回一个 x,那么当作[[Resolve]](promise2, x)来处理,就跟上面的 resolve 一样处理,注意如果函数什么都没有返回,就是返回的 undefined
  •  promise1.then 函数中的两个回调函数只要有一个报错,那么直接调用 promise2.then 函数中的错误回调
  •  如果 promise1.then 的第一个回调不是函数,并且 promise1 调用的是 resolve,那么 promise2.then 的第一个回调参数是 promise1 中 resolve 函数的抛出值
  •  同理,如果 promise1.then 第二个回调不是函数,并且 promise1 调用的是 reject,那么 promise2.then 中的错误回调就会执行

思考

如果像上面这么说的话,这个新抛出来的 promise 何时调用这个 resolve 或者 reject 是一个关键, 并且这个抛出的 promise 的执行还得看 onFulfilled 和 onRejected 返回值,这一点当时写 promise 的时候想了很久,不知道如何组织,后来实在想不出来,看了下网上很多文章,发现这些逻辑都是在 PromiseCopy 主体里面实现的。

return new PromiseCopy((resolve, reject) => {});

then 实现加强版 

  1. PromiseCopy.prototype.then = function (onFulfilled, onRejected) {  
  2.   const self = this 
  3.   // 这个一定要这么写目的为了让值传递  
  4.   onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (val) => val;  
  5.   // 这个一定要这么写,一定要抛出一个错throw err  
  6.   onRejected =  
  7.     typeof onRejected === "function"  
  8.       ? onRejected  
  9.       : (err) => {  
  10.           throw err;  
  11.         };  
  12.   const newnewPromise = new PromiseCopy((resolve, reject) => {  
  13.     if (self.info.status === "onFulfilled") {  
  14.       setTimeout(() => {  
  15.         try {  
  16.           // 如果onFulfilled不是一个函数resolve--self.info.value  
  17.           let value = self.info.value;  
  18.           // 这个注释不要,留着只是为了记录当时的思路  
  19.           // 这个加判断是为了防止then函数逇回调不是一个函数,,是一个字符串  
  20.           //   if (typeof onFulfilled === "function") {  
  21.           //     value = onFulfilled(value);  
  22.           //   }  
  23.           value = onFulfilled(value);  
  24.           // 这里要做一个[[Resolve]](promise2, x)处理了  
  25.           // 因为resolve里面直接做了,所以直接调用,和网上的一些实现有点不一样  
  26.           // 他们是提取了一个resolvePromise函数调用,我是直接调用了resolve  
  27.           resolve(value);  
  28.         } catch (e) {  
  29.           reject(e);  
  30.         }  
  31.       });  
  32.     }  
  33.     // 注意这里根据上面可知onFulfilled,onRejected抛出的值都要经过[[Resolve]](promise2, x)  
  34.     // 这和resolve,reject不一样,promise中resolve才走[[Resolve]](promise2, x),reject不走  
  35.     if (self.info.status === "onRejected") {  
  36.       setTimeout(() => {  
  37.         try {  
  38.           let { value } = self.info;  
  39.           value = onRejected(self.info.value); 
  40.           resolve(value);  
  41.         } catch (e) {  
  42.           reject(e);  
  43.         }  
  44.       });  
  45.     }  
  46.     // 如果是pending状态也需要push  
  47.     if (self.info.status === "pending") {  
  48.       self.onFulfilledArr.push((data) => {  
  49.         setTimeout(() => {  
  50.           try {  
  51.             let value = data 
  52.             value = onFulfilled(value);  
  53.             resolve(value);  
  54.           } catch (e) {  
  55.             reject(e);  
  56.           }  
  57.         });  
  58.       }); 
  59.       self.onRejectedArr.push((data) => {  
  60.         setTimeout(() => {  
  61.           try {  
  62.             let value = data 
  63.             value = onRejected(data);  
  64.             resolve(value);  
  65.           } catch (e) {  
  66.             reject(e); 
  67.            }  
  68.         });  
  69.       });  
  70.     }  
  71.   });  
  72.   return newPromise;  
  73. }; 

小结

到这里 promise 的主体实现已经完成了,下面是测试结果

Promise 其他静态方法

Promise.resolve 

  1. PromiseCopy.resolve = function (data) {  
  2.   return new PromiseCopy((resolve, reject) => {  
  3.     resolve(data);  
  4.   });  
  5. }; 

reject 

  1. Promise.reject = function (reason) {  
  2.   return new Promise((resolve, reject) => {  
  3.     reject(reason);  
  4.   });  
  5. }; 

Promise.all

这个方法有几个特点如下

  •  该方法接受一个数组,数组每一个元素都是一个 promise 对象
  •  只有所有 promise 都是 onFulfilled 的时候才会执行 then 回调,并且结果顺序和数组的一致
  •  如果其中一个 promise 发生了 reject 那么就会返回这个值 
  1. PromiseCopy.all = function (data) {  
  2.   let count = 0; // 记录调用次数  
  3.   let total = data.length;  
  4.   let result = []; 
  5.   return new PromiseCopy((resolve, reject) => {  
  6.     for (let i = 0; i < total; i++) {  
  7.       data[i].then(  
  8.         (res) => {  
  9.           result.push(res);  
  10.           ++count;  
  11.           if (count === totla) {  
  12.             resolve(result);  
  13.           }  
  14.         },  
  15.         (res) => {  
  16.           return reject(res);  
  17.         }  
  18.       );  
  19.     }  
  20.   });  
  21. }; 

Promise.race

这个方法也有以下几个特点

  •  这个方法也是接受数组,数组的元素是 promise
  •  他只返回最快的那一个 promise 的值
  •  就算有错误也会返回最快那一个 promise 的值 
  1. PromiseCopy.race = function (data) {  
  2.   const total = data.length;  
  3.   return new PromiseCopy((resolve, reject) => {  
  4.     for (let i = 0; i < total; i++) {  
  5.       data[i].then(  
  6.         (res) => {  
  7.           resolve(res);  
  8.         },  
  9.         (res) => {  
  10.           return reject(res);  
  11.         }  
  12.       );  
  13.     }  
  14.   });  
  15. }; 

catch 方法 

  1. PromiseCopy.prototype.catch = function (onRejected) {  
  2.   // 能到catch里面来的一定是走的reject的  
  3.   // 而且状态一定是pendding  
  4.   const self = this 
  5.   const newnewPromise = new PromiseCopy((resolve, reject) => {  
  6.     if (self.info.status === "onRejected") {  
  7.       try {  
  8.         setTimeout(() => {  
  9.           let { value } = self.info;  
  10.           if (typeof onRejected === "function") {  
  11.             value = onRejected(self.info.value);  
  12.           }  
  13.           resolve(value);  
  14.         });  
  15.       } catch (e) {  
  16.         rejetc(e);  
  17.       } 
  18.     }  
  19.     if (self.info.status === "pending") {  
  20.       self.onRejectedArr.push((data) => {  
  21.         setTimeout(() => {  
  22.           try {  
  23.             let value = data 
  24.             if (typeof onRejected === "function") {  
  25.               value = onRejected(data);  
  26.             }  
  27.             resolve(value);  
  28.           } catch (e) {  
  29.             reject(e);  
  30.           }  
  31.         });  
  32.       });  
  33.     }  
  34.   });  
  35.   return newPromise;  
  36. };  
  37. // 后来发现catch有一个简单的实现方法  
  38. // 没有删除上面就是为了记录思路过程  
  39. Promise.prototype.catch = function (onRejected) {  
  40.   return this.then(null, onRejected);  
  41. }; 

deferred

这个是 Promise 提供的一个快捷使用,自己实现 promise 的时候一定要加,不然 promises-aplus-tests promise.js 跑不过 

  1. PromiseCopyPromiseCopy.defer = PromiseCopy.deferred = function () {  
  2.   let dfd = {};  
  3.   dfd.promise = new PromiseCopy((resolve, reject) => {  
  4.     dfd.resolve = resolve;  
  5.     dfd.reject = reject;  
  6.   });  
  7.   return dfd;  
  8. }; 

源码

promise 源码已经上传到个人 github ( https://github.com/yizhengfeng-jj/promise 欢迎 star) 

 

责任编辑:庞桂玉 来源: 前端大全
相关推荐

2022-02-11 13:44:56

fiber架构React

2021-04-07 20:01:23

Go变量常量

2020-04-28 22:12:30

Nginx正向代理反向代理

2022-11-11 08:19:03

redis分布式

2022-04-25 09:03:16

JavaScript代码

2017-07-20 16:55:56

Android事件响应View源码分析

2022-03-26 08:49:13

MySQL数据存储

2017-12-05 17:44:31

机器学习CNN卷积层

2020-10-14 08:50:38

搞懂 Netty 线程

2024-01-03 13:39:00

JS,Javascrip算法

2023-10-18 10:55:55

HashMap

2019-07-23 08:55:46

Base64编码底层

2019-11-14 05:22:41

Javascript语言this

2019-06-26 06:31:56

缓冲缓冲池查询数据

2024-06-21 08:32:24

2019-06-24 05:05:40

缓冲池查询数据InnoDB

2019-06-26 09:41:44

分布式事务微服务

2021-02-26 05:29:11

排序算法数组

2021-02-22 07:29:07

算法初级排序

2023-05-29 08:12:38

点赞
收藏

51CTO技术栈公众号