javaScript 进阶之路 --- 《手写 Promise(后篇)》(二)

简介: javaScript 进阶之路 --- 《手写 Promise(后篇)》

五. 解决 Bug


那现在这个事情怎么解决呢?没错,数组!我们只需要将原来的 callBackFn 由一个单纯的变量,改造成一个数组。

image.png

紧接着去改造我们的 then 方法。

image.png

稍等,我想你有很大概率会按照上面的写法这样去写,其实这样是非常错误的,你这样的逻辑是把 onFulfilled(this.$result) 函数执行的结果推进数据中去,而不是把一个函数推进数组中去。所以正确的写法应该是我们用箭头函数包装一层,如下所示。

image.png

上面的代码含义是,如果我在保存异步数据,由于的 state 不能第一时间从 pending 状态改变,那我就先把一个箭头函数推进一个叫 callBackFnArray的数组中去。

那么去哪里执行我们数据里堆积的回调函数呢?这里我们就需要去改造一下我们的 resolve 函数。

image.png

我们需要把上面的老代码改造成下面的样子。

image.png

我们用数组身上的 forEach 得到每个箭头函数,然后依次调用即可。

ok,我们还是测试这个例子看一下我们的思路是否正确。

image.png

下面是控制台输出效果。

99ad1ea82a584bbc85c31ad4532f7f89_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg

是的,我们成功解决了这个棘手的问题。

六. then 函数可以链式调用的原因


我们知道原生 Promise 最大的特点就是可以允许我们连续 then 下去。如下所示。

image.png

我们不能光知道可以这样用,还要明确一点为什么可以这样用。首先,我们 then 函数的调用者是不是一个 Promise 实例啊?

image.png

那第二个 then 方法能调用,是不是就意味着这串代码也是一个 Promise🤔?

image.png

怎么验证呢?非常简单,我们打印一下它的返回值不就可以了吗?说干就干,我们用一个变量 test 去接收第一次 then 方法的返回值结果看看是个什么。

image.png

我们看一下控制台。果然是一个 Promise

image.png

分析完原生的 Promise,我们再分析一下我们的 MyPromise

image.png

image.png

  1. 很不幸的是,我们的 then 返回的是一个 undefinedundefined 身上有 then 方法吗?显然是没有的,如何解决?我们接着往下看。目前为止,你的代码应该是这个样子。为了方便你们进行下一步,我直接贴上源码,如果你们有哪一步没跟上,可以根据我现在的代码做出一些调整。
class MyPromise {
  #result: any;
  #state: "pending" | "fulfilled" | "rejected";
  #callBackFnArray: Array<any>;
  constructor(executor: Function) {
    this.#callBackFnArray = [];
    this.#state = "pending";
    executor(this.resolve.bind(this), this.reject.bind(this));
  }
  resolve(value) {
    if (this.#state !== "pending") return;
    this.#result = value;
    this.#state = "fulfilled";
    if (this.#callBackFnArray.length > 0) {
      queueMicrotask(() => {
        this.#callBackFnArray.forEach((onFulfilled) => {
          onFulfilled();
        });
      });
    } else {
      return;
    }
  }
  reject(value) {
    if (this.#state !== "pending") return;
    this.#result = value;
    this.#state = "rejected";
  }
  then(onFulfilled, onRejected) {
    if (this.#state === "pending") {
      this.#callBackFnArray.push(() => {
        onFulfilled(this.#result);
      });
    } else if (this.#state === "fulfilled") {
      queueMicrotask(() => {
        onFulfilled(this.#result);
      });
    } else if (this.#state === "rejected") {
      onRejected(this.#result);
    }
  }
}

七. 实现 then 函数的链式调用


既然我们知道了要想实现 then 方法的链式调用,那么 then 方法本身的返回值就也需要是一 Promise 实例。那还想什么呢,我们直接看 then 函数的结构体。

image.png

我们直接返回一个新的 MyPromise 实例,然后最最最重要的关键点,也是我们之前一直在强调的一点,你一定要记住!

MyPromise 的参数 execurtor 是一个会立即执行的普通函数。

既然会立即执行,我们把之前 then 函数里的那逻辑代码放进去是不是没有任何影响?操作非常简单,只需要把之前的代码复制粘贴进我们的 executor 函数内即可。< /br>

image.png

结果如下:

image.png

这仅仅是第一步,看过我之前分析- 《手写“回调地狱”》的读者都知道。我们下一个 then 方法读取的结果是上一个 onFulfilled 或者 onRejected 的返回值。那么我们在这里如何读取呢?

image.png

注意我上面这两段代码的含义。

image.png

这个函数的执行结果是不是就代表着 onFulfilled 函数执行完毕的返回值呢?当下面这个两个箭头函数执行的时候,就会将 onFulfilled(this.#result) 的结果通过回调函数的形式传递给我们。

image.png

这里确实是一个难点,需要读者深刻去体会下一个 resolve 为了去保存数据而又作为了一个回调函数的意思

最后我们测试一下是否可以正常链式调用。

image.png

经过千辛万苦,最后我们完成了 then 的链式调用

d58648e74a8f488c9d7629cfd6180645_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.jpg

八. 源码


在这里贴出我们最终实现的 MyPromise 源码。

class MyPromise {
  #result: any;
  #state: "pending" | "fulfilled" | "rejected";
  #callBackFnArray: Array<any>;
  constructor(executor: Function) {
    this.#callBackFnArray = [];
    this.#state = "pending";
    executor(this.resolve.bind(this), this.reject.bind(this));
  }
  resolve(value) {
    if (this.#state !== "pending") return;
    this.#result = value;
    this.#state = "fulfilled";
    if (this.#callBackFnArray.length > 0) {
      queueMicrotask(() => {
        this.#callBackFnArray.forEach((onFulfilled) => {
          onFulfilled();
        });
      });
    } else {
      return;
    }
  }
  reject(value) {
    if (this.#state !== "pending") return;
    this.#result = value;
    this.#state = "rejected";
  }
  then(onFulfilled, onRejected?) {
    return new MyPromise((resolve, reject) => {
      if (this.#state === "pending") {
        this.#callBackFnArray.push(() => {
          resolve(onFulfilled(this.#result));
        });
      } else if (this.#state === "fulfilled") {
        queueMicrotask(() => {
          resolve(onFulfilled(this.#result));
        });
      } else if (this.#state === "rejected") {
        onRejected(this.#result);
      }
    });
  }
}

九. 结语


其实在我写 《js进阶之路》专栏,从第一篇开始就感触颇深,不知道我这种一点一点知识点碎碎的讲是否能符合现在快节奏的阅读习惯...我只能站在我的观众都是小白的角度去考虑每一个问题,包括之前的文章都是。

慢慢地从自己写博客中就逐渐体会到,阮一峰,阮大,张鑫旭, 旭神等众多博主在写出一篇尽量能让小白去理解他们想表达出的知识点的不易和艰辛,但也正是有了他们,我们才能避免踩更多的坑。我也是从一只小白走过来的,我也深知学习的不易,本篇忽略了 reject 方法,其实原理和 resolve 是一模一样的,还希望你自己举一反三推出来, rejectonRejected 的写法。如果你觉得本篇对你有帮助,还希望能分享给你一同学习路上的小伙伴。

正因为淋过雨,所以想为后来者撑一把伞☔️~

也在这里恭喜自己,《手写Promise》 完结撒花 🎉


相关文章
|
15天前
|
前端开发 JavaScript
用JavaScript 实现一个简单的 Promise 并打印结果
用 JavaScript 实现一个简单的 Promise 并打印结果
|
15天前
|
JSON 前端开发 JavaScript
在 JavaScript 中,如何使用 Promise 处理异步操作?
通过以上方式,可以使用Promise来有效地处理各种异步操作,使异步代码更加清晰、易读和易于维护,避免了回调地狱的问题,提高了代码的质量和可维护性。
|
20天前
|
JSON 前端开发 JavaScript
浅谈JavaScript中的Promise、Async和Await
【10月更文挑战第30天】Promise、Async和Await是JavaScript中强大的异步编程工具,它们各自具有独特的优势和适用场景,开发者可以根据具体的项目需求和代码风格选择合适的方式来处理异步操作,从而编写出更加高效、可读和易于维护的JavaScript代码。
22 1
|
1月前
|
前端开发 JavaScript 开发者
JavaScript 中的异步编程:深入了解 Promise 和 async/await
【10月更文挑战第8天】JavaScript 中的异步编程:深入了解 Promise 和 async/await
|
1月前
|
前端开发 JavaScript 小程序
JavaScript的ES6中Promise的使用以及个人理解
JavaScript的ES6中Promise的使用以及个人理解
18 1
|
2月前
|
前端开发 JavaScript
JavaScript中的Promise:简化异步编程
JavaScript中的Promise:简化异步编程
|
2月前
|
Web App开发 前端开发 JavaScript
js之 Promise | 12-8
js之 Promise | 12-8
|
1月前
|
前端开发 JavaScript UED
深入了解JavaScript异步编程:回调、Promise与async/await
【10月更文挑战第11天】深入了解JavaScript异步编程:回调、Promise与async/await
19 0
|
2月前
|
前端开发 JavaScript
ES6新标准下JS异步编程Promise解读
ES6新标准下JS异步编程Promise解读
36 3
|
2月前
|
前端开发 JavaScript
JavaScript Promise-2
JavaScript Promise-2
27 3
下一篇
无影云桌面