likes
comments
collection
share

《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之

作者站长头像
站长
· 阅读数 28

引文:关于手写源码这件事,几乎是每个抠图仔逃不了的命运,手写Promise也是一样。但是也看到好多人说手写源码完全是浪费时间,可是很多时候你发发现收藏 !== 学会!抛开面试不说,通过源码习得的思想或许才是我们真正需要汲取的养分!况且能自己实现一个工具库或者Api不也是一件令人无比兴奋的事吗?😏

而手动实现一个Promise网上的资料非常之多,可是要么是功能不够全面,要么就是没有过程,于是我决定从0-1实现一个完整版的Promise,包括函数式和Class两个版本每个分支管理一个版本,包含当前版本非常详尽的迭代文档和源码

主打一个你不想看懂都难!

Promise是什么?

想要动手写一个Promise,我们首先得知道 Promise 是什么?我们都知道Promise是es6的新特性,阮一峰老师的这篇Promise文章堪称吾辈之楷模!无比详尽的介绍了Promise的实现,初学者可以看看这篇文章。

从0-1开启你的Promise实现之旅

我们将通过6个版本的迭代,对应王者农药中“六个段位” 😏 逐步实现函数式class两个版本的Promise,最终会实现一个具备如下功能的Promise,目标是无限趋近于Promise,后期会借鉴TDD的思想,接入测试用例以及其它特性(欢迎一起共建) 快来看看你在哪个段位吧?

《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 看一眼得了,别太较真哈,不然容易被劝退咯! 《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 那我们废话不多说,直接开始吧?

1.0版本Promise(倔强废铁)

目标

请先认真看一下我们1.0版本的目标,字数不多,1min不到,目的就是搭好架子(这一步是理解Promise的第一步,也是我认为最重要的一步)。 《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之

实现

《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 足够简单吧?至此,我们Promise的架子已经搭起来啦!不管做任何事情,如果你觉得有难度,那一定就是拆分的不是足够小足够细,你细品是不是这个道理😏

2.0版本Promise(傲娇白银)

目标

2.0的Promise是时候上点硬菜啦?我们需要做的比较多,我们需要实现resolvereject,也需要实现实例对象的then和catch方法!另外构造器函数内部我们需要一个变量保存resolve和reject的执行结果,也需要支持基本的异常处理《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之

实现

构造器相关属性

  this.PromiseState = "pending";
  // 保存Promise对象的结果
  this.PromiseResult = null;
  // 保存异步操作场景的回调函数
  this.callbacks = [];
  // resolve和reject普通函数内部的this指向的是window对象,所以需要保存this
  let self = this;

实现resolve

   * 成功的回调函数
   * @param {*} data
   * @returns
   */
  function resolve(data) {
    // Promise的状态只能被修改一次
    if (self.PromiseState !== "pending") return;
    // 修改Promise对象的状态为fulfilled;
    self.PromiseState = "fulfilled";
    self.PromiseResult = data;
    // 批量执行异步操作场景成功的回调函数
    self.callbacks.forEach((cb) => {
      cb.onResolved(data);
    });
  }

实现reject

   * 失败的回调函数
   * @param {*} data
   * @returns
   */
  function reject(data) {
    // Promise的状态只能被修改一次
    if (self.PromiseState !== "pending") return;
    // 修改Promise对象的状态为rejected;
    self.PromiseState = "rejected";
    self.PromiseResult = data;
    // 批量执行异步操作场景失败的回调函数
    self.callbacks.forEach((cb) => {
      cb.onRejected(data);
    });
  }

可能部分同学会有疑问:

  1. 为什么要保存PromiseResult呢?哪里才会用到呢?
  2. resolve和reject中批量执行的callbacks是哪里来的呢?
  3. 为啥callbacks是个数组呢?

别急,等我们实现then了方法,谜底自会全部揭晓!带着这3个问题,我们一起来实现一下then方法

实现then方法

 * 返回一个新的Promise对象
 * @param {*} onResolved
 * @param {*} onRejected
 */
Promise.prototype.then = function (onResolved, onRejected) {
  return new Promise((resolve, reject) => {
    if (this.PromiseState === "fulfilled") {
      onResolved(this.PromiseResult);
    }
    if (this.PromiseState === "rejected") {
      onRejected(this.PromiseResult);
    }

    // 保存异步操作场景的回调函数
    this.callbacks.push({
      onResolved,
      onRejected,
    });
  });
};

实现了then方法,相信前面的3个问题已经清楚了吧?

  1. 保存PromiseResult是因为then方法的两个回调函数中需要Promise的执行结果!

《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 2. 关于为啥要存callback的问题,以及为啥要用数组保存而不是对象,我们留个彩蛋?可以在评论区留下你的答案,也可以看看别人的优秀答案😏

实现catch方法

catch方法就比较简单了,因为我们已经实现了then,我们可以通过给then方法第二个参数传undefined的方式,直接服用then方法的第二个回调。

 * Promise实例对象的catch方法,也返回一个新的Promise对象,只是指定失败的回调函数
 * @param {*} onRejected
 * @returns
 */
Promise.prototype.catch = function (onRejected) {
  return this.then(undefined, onRejected);
};

现在我们的Promise已经具备了基本处理resolve和reject的能力,then中也能拿到resolve或reject的结果了,但是我们知道Promise的then的执行结果也是一个Promise对象,那then的结果长啥样呢? 先来看看官方的Promise的实现: 《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 嗯!是个Promise对象,而且是个fulfilled状态的Promise对象。再来看看我们自己的then: 《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 嗯?虽然也是个Promise对象,但是为啥是pending状态呢?再回到上面回顾一下我们实现的then,我们只是执行了相应的回调函数,并没有改变then这个Promise的状态以及处理其返回结果呀?

《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之

这里还有一个问题:为啥PromiseResult的结果是undefined呐?显然,我们需要带着这个问题,继续完善一下我们的then方法。

3.0版本Promise(出土黄金)

目标

这个版本我们只需带上上面的问题,完善一下then方法,核心目标是实现then方法内部状态和执行结果的处理,而then方法的结果和状态取决于其内部回调函数的执行结果,而这个执行结果通常分为3种:仍然是一个Promise对象是一个普通值以及throw的错误《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之

实现

 * 返回一个新的Promise对象
 * @param {*} onResolved
 * @param {*} onRejected
 */
Promise.prototype.then = function (onResolved, onRejected) {
  const self = this;
  return new Promise((resolve, reject) => {
    const callback = function (fnType) {
      try {
        let result = fnType(self.PromiseResult);

        if (result instanceof Promise) {
          result.then(
            (res) => {
              resolve(res);
            },
            (err) => {
              reject(err);
            }
          );
        } else {
          resolve(result);
        }
      } catch (error) {
        reject(error);
      }
    };
    // 保存成功和失败的回调函数
    if (this.PromiseState === "fulfilled") {
      callback(onResolved);
    }
    if (this.PromiseState === "rejected") {
      callback(onRejected);
    }

    // 保存异步操作场景的回调函数
    this.callbacks.push({
      onResolved,
      onRejected,
    });
  });
};

现在,我们的then也可以有自己的状态和结果啦!

《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 再来试试返回普通值以及Promise场景的结果:

《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之

《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 全部符合预期,都可以正常处理啦,欧耶!🍻

革命尚未结束,同志仍需努力呀!下一个版本,我们需要:

  1. 处理then回调函数中存在的异常穿透问题以及onResolved不传的问题
  2. 新增Promise的一系列热门方法:resolve、reject、all、race等
  3. 新增实例方法的finally方法。

let's play, let's go!

4.0版本Promise(酷炫铂金侠)

目标

目前上面已经大致介绍,大纲来啦!

《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之

实现

继续完善then方法,使其更强大

 * 返回一个新的Promise对象
 * @param {*} onResolved
 * @param {*} onRejected
 */
Promise.prototype.then = function (onResolved, onRejected) {
  const self = this;
  // 如果不传onResolved函数,就默认返回成功的结果
  if (typeof onResolved !== "function") {
    onResolved = (value) => value;
  }
  // 支持catch方法的异常穿透处理,如果不传onRejected函数,就默认抛出异常
  if (typeof onRejected !== "function") {
    onRejected = (err) => {
      throw err;
    };
  }

  return new Promise((resolve, reject) => {
    const callback = function (fnType) {
      try {
        let result = fnType(self.PromiseResult);

        if (result instanceof Promise) {
          result.then(
            (res) => {
              resolve(res);
            },
            (err) => {
              reject(err);
            }
          );
        } else {
          resolve(result);
        }
      } catch (error) {
        reject(error);
      }
    };
    // 保存成功的回调函数
    if (this.PromiseState === "fulfilled") {
      callback(onResolved);
    }
    // 保存失败的回调函数
    if (this.PromiseState === "rejected") {
      callback(onRejected);
    }

    // 保存异步操作场景的回调函数,先存起来,等到Promise对象的状态改变时再执行
    if (this.PromiseState === "pending") {
      this.callbacks.push({
        onResolved: function () {
          callback(onResolved);
        },
        onRejected: function () {
          callback(onRejected);
        },
      });
    }
  });
};

这版我们新增:

《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 以及

《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 新增实例对象的finally方法

 * Promise实例对象的finally方法,返回一个新的Promise对象
 * 1. 无论成功还是失败都会执行的回调函数
 * 2. 返回的Promise对象的状态和结果取决于传入的Promise对象的状态和结果
 * 3. 如果原始 Promise 成功,执行回调函数,然后返回一个新的 Promise,状态为 fulfilled,值为原始 Promise 的值
 * 4. 如果原始 Promise 失败,执行回调函数,然后返回一个新的 Promise,状态为 rejected,值为原始 Promise 的失败原因
 * @param {*} callback
 * @returns
 */
Promise.prototype.finally = function (callback) {
  return this.then(
    (value) => {
      return Promise.resolve(callback()).then(() => value);
    },
    (reason) => {
      return Promise.resolve(callback()).then(() => {
        throw reason;
      });
    }
  );
};

新增Promise的resolve方法

 * Promise.resolve方法,返回一个Promise对象,其状态和结果取决于传入参数的类型:
 * 1. 如果是一个Promise对象,那么返回的Promise对象的状态和结果取决于传入的Promise对象的状态和结果
 * 2. 如果是一个普通值,那么返回的Promise对象是fulfilled的Promise,结果就是传入的值
 * @param {*} data
 * @returns
 */
Promise.resolve = function (data) {
  return new Promise((resolve, reject) => {
    if (data instanceof Promise) {
      data.then(
        (res) => {
          resolve(res);
        },
        (err) => {
          reject(err);
        }
      );
    } else {
      resolve(data);
    }
  });
};

新增Promise的reject方法

reject比较特殊,不管你传入什么都不好使,结果始终是rejected的Promise对象

 * Promise.reject方法,返回一个Promise对象
 * 1. 不管传入的参数是什么,结果都是失败
 * 2. 返回的Promise对象的状态是rejected
 * 3. 返回的Promise对象的结果就是传入的参数
 * @param {*} data
 * @returns
 */
Promise.reject = function (data) {
  return new Promise((resolve, reject) => {
    reject(data);
  });
};

新增Promise的all方法

all只需铭记一点:所有成功则成功,只要有一个失败则失败。成功时返回所有Promise执行结果组成的数组,失败时返回失败的那一个的执行结果。

 * Promise.all方法,用于指定多个Promise对象同时执行:
 * 1. 只有全部成功才会成功,此时返回的Promise对象的结果是一个由所有Promise结果组成的数组,数组的顺序和传入的Promise对象的顺序一致
 * 2. 只要有一个失败就会失败,此时返回的Promise对象的结果是失败的Promise对象的结果
 * 3. 返回一个新的Promise对象
 * @param {*} promises
 */
Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    let count = 0;
    let result = [];
    for (let index = 0; index < promises.length; index++) {
      promises[index].then(
        (res) => {
          count++;
          // 不采用push的方式,因为push的方式会改变数组的顺序
          result[index] = res;
          if (count === promises.length) {
            resolve(result);
          }
        },
        (err) => {
          reject(err);
        }
      );
    }
  });
};

新增Promise的race方法

这让我想起了最近的“辛🐑大战”,谁先赢得市场先机谁就握有最高定价权和话语权!race的状态和结果取决于“跑的最快”的那个Promise对象!

 * Promise.race方法,返回一个 Promise 对象,谁第一个改变状态谁就有决定权!也就是决定 race 最终的状态和结果:
 * 1. 只要有一个成功就会成功,此时返回的Promise对象的结果是第一个成功的Promise对象的结果
 * 2. 只要有一个失败就会失败,此时返回的Promise对象的结果是第一个失败的Promise对象的结果
 * 3. 取决于第一个改变状态的Promise对象
 * @param {*} promises
 * @returns
 */
Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    for (let index = 0; index < promises.length; index++) {
      promises[index].then(
        (res) => {
          resolve(res);
        },
        (err) => {
          reject(err);
        }
      );
    }
  });
};

相信被面试官拷打过的抠图仔们都知道,“Promise的构造器是同步执行的,但是then方法是异步执行的”,那我们看看我们实现的是不是也这样呢?

写一个非常简单的demo测试一下,很显然,我们期望输出:111 => 222 => 333

《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 实际输出:111 => 333 => 222 ,打脸来的如此之快?几个意思?🙄 《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 仔细回顾一下就会发现:我们在resolve和reject中改变完状态后,压根就没有通过异步执行then呀,而是立刻就执行了then😱。那就继续完善呗?

5.0版本Promise(精灵钻石怪)

目标

5.0的目标很简单,将所有then、catch以及resolve和reject执行后callbacks中存储的方法的执行时机统统调整为异步。 《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之

实现

《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 其它几处都是相同的处理方式,不再重复演示。 至此,我们已经实现了Promise几乎所有常用的功能。什么?函数式的实现太low ?那再来改一个class版,说干就干!👊🏻

6.0版本Promise(假冒王者尊)

目标

改造为class版本的Promise

实现

重点在于实现的过程和思路,改造相对来说就容易太多啦! 《手撕源码系列专栏》带你从0到1实现一个完整版的Promise(附源码和文档)手动实现一个Promise网上的资料非常之 至此,我们手写版的Promise就已告一段落。是不是觉得Promise也没那么难搞呢?😏。当然我们实现的只是mini版的,属于“山寨版的Promise”,跟Promise本尊肯定是有一定的差距的!但是首先我们是麻雀虽小但也五脏俱全咯!而且要想无限靠近Promise本尊,还需各位的相助共建

《重学JavaScript专栏》其它文章推荐:

转载自:https://juejin.cn/post/7416723051377033252
评论
请登录