如何从无到有实现Promise(下)

温故知新

上一篇《如何从无到有实现Promise(上)》中我们已经实现了一个看似可以正常工作的简易版 Promise ,不要认为这样就结束了,其实好戏才刚刚开始。
本篇我们继续改造和丰富这个 Promise,让它可以适用更复杂的场景。

本文篇幅较长,又有大量的代码片段,可以边记录笔记边阅读,对照着看更容以理解。


链式调用“有点东西”

众所周知 Promisethen 方法是支持链式调用的,如下所示:

  • 链式调用场景1
      const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("first then");
        }, 2000);
      });

      promise
        .then((data) => {
          console.log(data);
          return "second then";
        })
        .then((data) => {
          console.log(data);
        });

输出结果应该为,先打印 first then ,两秒后打印 second then

但是我们目前实现的 Promise 是不支持的,那该如何实现 Promise 实例的 then 方法支持链式调用这个行为呢?

Promise 实例的 then 方法内的 onfulfilledonrejected 函数中,是支持再次返回一个 Promise 实例的,也支持返回一个普通值(非 Promise 实例);并且返回的这个 Promise 实例或者这个普通值将会传给下一个 then 方法里 onfulfilledonrejected 函数中,如此, then 方法就支持了链式调用。

如上总结,想要支持 then 的链式调用,就要每一个 then 方法的 onfulfilled 函数和 onrejected 函数都要返回一个 Promise 实例。
我们先支持链式调用 then 方法时返回一个普通的值的情况,改造 then 方法:

      Promise.prototype.then = function (onfulfilled = Function.prototype, onrejected = Function.prototype) {
        let promise2;

        if (this.status === "fulfilled") {
          return (promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
              try {
                let result = onfulfilled(this.resolveVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }

        if (this.status === "rejected") {
          return (promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
              try {
                let result = onrejected(this.rejectVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }

        if (this.status === "pending") {
          return (promise2 = new Promise((resolve, reject) => {
            this.onFulfilledArray.push(() => {
              try {
                let result = onfulfilled(this.resolveVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });

            this.onRejectedArray.push(() => {
              try {
                let result = onrejected(this.rejectVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }
      };

结合我们上面的分析,代码不难理解,当调用 Promise 实例 then 方法的时候,应该再次返回一个 Promise 实例,promise2 就将作为 then 方法的返回值。

这个 promise2 是什么时候被 resolve 或者 reject 的呢?

  • 当判断分支是 status === "fulfilled"或者 status === "rejected"的时候!

这个 promise2 在执行完 then 方法时就被 resolve 或者 reject 了。

  • 当判断分支为 status === "pending"时情况较为复杂!

返回的 promise2 实例的 resolvereject 是放到 onFulfilledArrayonRejectedArray数组中的。
当异步执行完成后,依次执行 onFulfilledArrayonRejectedArray 数组内的函数时才执行了 promise2resolvereject
那么在 onFulfilledArrayonRejectedArray 数组中的函数内应该 resolve 或是 rejectpromise2,并且传入的参数就是 onfulfilled 或者 onrejected 的执行结果。

这样 then 方法支持链式调用,并且支持返回一个普通值的情况。
再来整体看下目前为止的完整代码:

      function Promise(excutor) {
        this.status = "pending";
        this.resolveVal = null;
        this.rejectVal = null;
        this.onFulfilledFuncArray = [];
        this.onRejectedFuncArray = [];

        const resolve = (value) => {
          setTimeout(() => {
            if (this.status === "pending") {
              this.resolveVal = value;
              this.status = "fulfilled";
              this.onFulfilledFuncArray.forEach((fn) => {
                fn(this.resolveVal);
              });
            }
          }, 0);
        };

        const reject = (error) => {
          setTimeout(() => {
            if (this.status === "pending") {
              this.rejectVal = error;
              this.status = "rejected";
              this.onRejectedFuncArray.forEach((fn) => {
                fn(this.rejectVal);
              });
            }
          }, 0);
        };

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

      Promise.prototype.then = function (
        onfulfilled = Function.prototype,
        onrejected = Function.prototype
      ) {
        let promise2;

        if (this.status === "fulfilled") {
          return (promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
              try {
                let result = onfulfilled(this.resolveVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }

        if (this.status === "rejected") {
          return (promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
              try {
                let result = onrejected(this.rejectVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }

        if (this.status === "pending") {
          return (promise2 = new Promise((resolve, reject) => {
            this.onFulfilledArray.push(() => {
              try {
                let result = onfulfilled(this.resolveVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });

            this.onRejectedArray.push(() => {
              try {
                let result = onrejected(this.rejectVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }
      };

上面也说了,then 方法也是支持显式返回一个 Promise 实例的情况,如下。

  • 链式调用场景2
      const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("first then");
        }, 2000);
      });
      promise
        .then((data) => {
          console.log(data);
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve("second then");
            }, 2000);
          });
        })
        .then((data) => {
          console.log(data);
        });

第一种情况是 then 方法的 onFulfilledonrejected 函数返回的是一个普通值,与之不同的是这里我们要支持 onFulfilledonrejected 函数返回的是一个 Promise 实例。

      let result = onfulfilled(this.resolveVal);
      let result = onrejected(this.rejectVal);

场景1中,上面两行代码得到的结果都是一个普通值,也就是说 result 就是一个普通值,现在要做的是让 result 可以为普通值,也可以为 Promise 实例。所以我们就不能直接对 result 进行 resolve(result) 的操作。

综上所述,我们抽象出 resolvePromise 方法进行统一处理,替换原 resolve 方法。
接下来就要完成这个 resolvePromise 函数:
首先我们定义方法参数:

  • promise2:返回的 Promise 实例
  • result:onfulfilled 或者 onrejected 函数的返回值
  • resolve: promise2resolve 方法
  • reject: promise2reject 方法

方法实现为:

      const resolvePromise = (promise2, result, resolve, reject) => {
        if ((typeof result === "function" || typeof result === "object") && result !== null) {
          try {
            if (typeof result.then === "function") {
              result.then.call(
                result,
                function (value) {
                  return resolvePromise(promise2, value, resolve, reject);
                },
                function (error) {
                  return reject(error);
                }
              );
            } else {
              resolve(result);
            }
          } catch (e) {
            return reject(e);
          }
        } else {
          resolve(result);
        }
      };

看不懂不要急,此处是本文最大的难点了,也是链式调用的核心所在,接下来将目前的代码整合在一起,然后仔细分析下 resolvePromise 究竟做了什么。
附加注释的完整代码如下:

      function Promise(executor) {
        this.status = "pending";
        this.resolveVal = null;
        this.rejectVal = null;
        this.onFulfilledArray = [];
        this.onRejectedArray = [];

        const resolve = (value) => {
          setTimeout(() => {
            if (this.status === "pending") {
              this.resolveVal = value;
              this.status = "fulfilled";

              this.onFulfilledArray.forEach((fn) => {
                fn(this.resolveVal);
              });
            }
          });
        };

        const reject = (error) => {
          setTimeout(() => {
            if (this.status === "pending") {
              this.rejectVal = error;
              this.status = "rejected";

              this.onRejectedArray.forEach((fn) => {
                fn(this.rejectVal);
              });
            }
          });
        };

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

      // 参数1:promise2实例  参数2:onfulfilled 和 onrejected 执行结果   参数3、4:promise2 的 resolve 以及 reject
      const resolvePromise = (promise2, result, resolve, reject) => {
        // 如果返回数据可能是 Promise 类型(再进行 .then 判断后才可真正确认,在下面处理)
        if (
          (typeof result === "function" || typeof result === "object") &&
          result !== null
        ) {
          try {
            // 通过 then 方法判断可以确定是否是 Promise 类型
            if (typeof result.then === "function") {
              // 执行 then 方法 , 参数分别为 onfulfilled 和 onrejected
              result.then.call(
                result,
                function (value) {
                  // onfulfilled 函数
                  // 当 result 是 Promise 类型时,递归调用 resolvePromise 函数,直到 result 不再是 Promise 类型,执行 promise2 (当前then的返回promise)的 resolve。
                  return resolvePromise(promise2, value, resolve, reject);
                },
                function (error) {
                  // onrejected 函数
                  return reject(error);
                }
              );
            } else {
              // result 不是Promise类型,直接 resolve promise2
              resolve(result);
            }
          } catch (e) {
            return reject(e);
          }
        } else {
          // result 不是Promise类型,直接 resolve promise2
          resolve(result);
        }
      };

      Promise.prototype.then = function (onfulfilled = Function.prototype, onrejected = Function.prototype) {
        // Promise 需要支持链式调用,所以 then 方法也要返回一个 Promise 实例
        let promise2;

        // 因为 resolve 函数内逻辑是异步执行的,所以只有 then 方法被异步调用,才会进入这个分支,在执行 then 方法的时候,resolve 操作已经完成,状态已经变更
        if (this.status === "fulfilled") {
          return (promise2 = new Promise((resolve, reject) => {
            // 返回的 promise2 中的代码要异步执行
            setTimeout(() => {
              try {
                // 执行 onfulfilled 函数,得到返回结果。
                let result = onfulfilled(this.resolveVal);
                // 得到结果 result 可能是普通值,可能依然是 Promise 实例,通过 resolvePromise 进行处理
                // 并且在 resolvePromise 函数中进行 promise2 的 resolve 或者 reject 操作
                resolvePromise(promise2, result, resolve, reject);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }

        // 与上面同理
        if (this.status === "rejected") {
          return (promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
              try {
                let result = onrejected(this.rejectVal);
                resolvePromise(promise2, result, resolve, reject);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }

        // 因为 resolve 函数内逻辑是异步执行的,因此当 then 方法被同步调用的时候,resolve 内的逻辑还未执行,状态依然是 pending
        // 此时需要保存调用 then 方法时传入的 onfulfilled 和 onrejected 函数,在 resolve 执行时再取出执行
        if (this.status === "pending") {
          return (promise2 = new Promise((resolve, reject) => {
            // 因为同一个 Promise 实例可能有多个 then 方法,所以将所有 then 方法内的 onfulfilled 函数进行保存,需要时依次执行
            this.onFulfilledArray.push((value) => {
              try {
                // 执行 onfulfilled 函数,得到返回结果。
                let result = onfulfilled(value);
                // 得到结果 result 可能是普通值,可能依然是 Promise 实例,通过 resolvePromise 进行处理
                // 并且在 resolvePromise 函数中进行 promise2 的 resolve 或者 reject 操作
                resolvePromise(promise2, result, resolve, reject);
              } catch (e) {
                reject(e);
              }
            });

            this.onRejectedArray.push((error) => {
              try {
                let result = onrejected(error);
                resolvePromise(promise2, result, resolve, reject);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }
      };

这应该是两篇文章到现在最接近最终实现并且注释最详细的一次了。不过依然不能改变它非常难以理解的事实,理解的关键在于弄清楚当 resultPromise 类型时,和 promise2 的关系,以及当 resultPromise 类型时递归调用 resolvePromise 函数的目的

没有什么捷径,只能多看多写多思考吧,写出测试代码,然后顺着执行顺序去跟踪代码。
我想出了这个流程图尽可能地帮助大家理解。
先看测试代码:

      const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("first then");
        }, 2000);
      });

      promise
        .then((data) => {
          console.log(data);
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              reject(`second then`);
            }, 2000);
          });
        })
        .then((data) => {
          console.log(data);
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve(`last then`);
            }, 2000);
          });
        })
        .then((data) => {
          console.log(data);
        });

2秒后输出 'first then' 再过2秒后输出 'second then' 再过2秒后输出 'last then'

在这里插入图片描述

回过头来再看,其实 resolvePromise 方法的作用非常明确,当 onfulfilled 函数返回的数据(result)为普通值的话,还是像场景1一样直接 resolve promise2 处理即可,但是如果当这个 resultPromise 类型时,就要在 result.then.onfulfilled 中去递归调用 resolvePromise ,当再进去 resolvePromise 的时候,此时新的 result 参数如果是普通值了,就 resolve promise2 ,并将结果作为参数返回即可。

到这如果你都可以理解,可以说是基本掌握了 Promise 的除静态方法外的全部基础内容。为了简化代码,更容易理解,一些容错机制没有添加,并不影响整体思路的学习。


静态方法,不说你也会

静态方法其实非常简单,不说你也应该会,但是我还是简单说下吧[捂脸]。
关于 Promise 的静态方法如 Promise.resolvePromise.rejectPromise.all 等等,就不再一一实现了,只选择平时用的比较多的 Promise.all 实现以下。

  • Promise.all 的实现

Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);
如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

看下具体使用。
场景1:

      const promise1 = new Promise((resolve, reject) => {
        resolve("p1");
      });

      const promise2 = new Promise((resolve, reject) => {
        resolve("p2");
      });

      Promise.all([promise1, promise2]).then((data) => {
        console.log(data);
      });

打印出 ["p1", "p2"]

场景2:

      const promise1 = new Promise((resolve, reject) => {
        resolve("p1");
      });

      const promise2 = new Promise((resolve, reject) => {
        reject("p2 失败");
      });

      Promise.all([promise1, promise2])
        .then((data) => {
          console.log(data);
        })
        .catch((error) => {
          console.log(error);
        });

打印出 p2 失败

对照着使用,来实现这个 Promise.all

      Promise.all = function (promiseArray) {
        if (!Array.isArray(promiseArray)) {
          throw new TypeError("promiseArray should be array!");
        }

        // 在外层包裹一个 Promise ,如果内部有一个 Promise 执行不成功,就执行最外层 Promise 的 reject
        return new Promise((resolve, reject) => {
          try {
            let resultArray = [];

            for (let i = 0; i < promiseArray.length; i++) {
              promiseArray[i].then((data) => {
                // 记录 resolve 的值
                resultArray.push(data);

                // 全部完成后 执行外出 Promise 的 resolve 将存放每一个成功值得数组返回即可
                if (resultArray.length === promiseArray.length) {
                  resolve(resultArray);
                }
              }, reject);
            }
          } catch (e) {
            reject(e);
          }
        });
      };

实现起来非常容易,其他几个静态方法思路也大同小异,有时间可以自行补充。


结束啦!

总结 Promise 的两篇笔记终于结束啦,实现 Promise 并不是目的,况且也不是完全按照规范去实现的,目的是学习思路,深层次得理解原理,达到融会贯通,这样以后不论是自己的代码设计参考它的实现原理还是在 Promise 的使用上遇到问题,解决起来都会得心应手。

这部分内容还是比较难理解的,整理笔记得时候也会有很多地方需要静下心来梳理思路。刚接触一头雾水也再正常不过了,不要灰心,多看多写多想,每看一次都会有不同的收获。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容