Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 编码专题

    • 00.React-目录
    • 10.React-组件-组件封装
    • 11.React-hooks-自定义钩子
    • 21.函数技巧 - 函数式编程之 compose 函数
    • 22.函数技巧-tapable钩子
    • 23.函数技巧-koa源码
    • 30.Promise系列-目录
    • 31.Promise-深入Promise原理
      • 0.前言
      • 1.实现 MVP 版本的代码
      • 2. 支持链式调用及结果传递
      • 3. 注意:函数库调用时设置严格模式
      • 4. 处理异常回调结果
      • 5.微任务 PolyFill 方案
      • 6. 各种静态函数实现
        • 6.1 resolve[有些问题]
        • 6.2 reject
        • 6.3 all
        • 6.4 allSettled
        • 6.5 race
        • 6.6 any
        • 6.7 finally
    • 32.Promise-指令式Promise改造
    • 32.promise-梳理 Promise 错误处理
    • 34.promise-竞态问题
    • 35.Promise-如何处理异步数组
  • 深入浅出 Vite

  • 快速上手 API

  • 深入浅出Babel

  • 深入浅出 React

  • 百问掘金
  • 编码专题
wangjiasheng
2023-04-27
目录

31.Promise-深入Promise原理

# 0.前言

本节博客代码仓库 (opens new window)

本篇博客一开始想的标题是《从零基于 PromiseA+ 实现 Promise 源码》,但是这种文章写出来,就是一堆 Promise 源码的底层实现,不利于对知识的分解与重构。

# 1.实现 MVP 版本的代码

假设不存在 Promise 这个概念,我们就是希望实现一个状态管理类,支持如下功能:

  1. 实现一个类库,该类有三个状态:pending | fulfilled | rejected
  2. 通过接受一个 callback 函数实例化这个类,即 new Promise(callback)
  3. 主体 callback 回调函数,需暴露出两个改变状态的函数。(resolve,reject)=>{}。
    • 当调用 resolve() 函数时,将类的状态修改为 fulfilled。
    • 当调用 reject() 函数时,将类的状态修改为 rejected。
  4. 使用 .then 可根据状态触发不同的分支。当 pending 状态时,不会继续执行。当fulfilled 或者 rejected 时代码可以继续流通。

话不多说啊,开干,本案例的写法基于 TDD 测试驱动开发,先完成测试案例,再一步一步实现。这种开发的好处是:

  1. 测试案例本身就是一份非常完善的文档。
  2. 分步实现功能,不用一开始就把所有细节都考虑的完善,在一次次迭代中完善案例。

首先,先基于 vitest 搭建基础测试工程,具体过程可见《vitest 测试框架环境搭建》 这篇博客。

将核心逻辑编写在 src/promiseCore.ts 文件中,测试代码 src/promiseCore.test.ts 文件。

基础测试代码:

import { describe, expect, it, vi } from "vitest";
import { PromiseCore as Promise } from "./promiseCore";

/* 如果所有用例都并发执行的话,可以使用 describe.concurrent */
describe("Promise", () => {
  it("1.保证是一个类", () => {
    expect(Promise).toBeTypeOf("function");
  });
  it("2.接受一个 `callback` 函数可以实例化这个类", () => {
    expect(() => new Promise()).toThrowError("请输入函数");
    /* @ts-ignore */
    expect(() => new Promise(1)).toThrowError("请输入函数");
    /* @ts-ignore */
    expect(() => new Promise(true)).toThrowError("请输入函数");
    /* new Promise(fn) 中的 fn 立即执行 */
    const fn = vi.fn();
    new Promise(fn);
    expect(fn).toHaveBeenCalled();
  });
  it("3.callback 接受 resolve 和 reject 两个函数", () => {
    new Promise((resolve, reject) => {
      expect(resolve).toBeTypeOf("function");
      expect(reject).toBeTypeOf("function");
    });
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

基础实现如下:

class PromiseCore {
  state: "pending" | "fulfilled" | "rejected" = "pending";
  /* 接受案例完成实例化构建 */
  constructor(fn: Function) {
    if (typeof fn !== "function") {
      throw new Error("请输入函数");
    }
    /* 暴露出两个函数,先用空函数实现 */
    fn(this.resolve.bind(this), this.reject.bind(this));
  }

  /* 原形方法 */
  resolve() {
    /* 待完善 */
  }
  reject() {
    /* 待完善 */
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

上面的写法虽然简单,但是过前面的测试,接下来要实现 .then 以及修改状态这部分功能了。

import { describe, expect, it, vi } from "vitest";
import { PromiseCore as Promise } from "./promiseCore";

/* 如果所有用例都并发执行的话,可以使用 describe.concurrent */
describe("Promise", () => {
  it("4.then 支持两个函数,也可不传", () => {
    new Promise((resolve, reject) => {}).then(null, null);
    new Promise((resolve, reject) => {}).then(
      () => {},
      () => {},
    );
  });

  it("4.1 promise.then(success) 中的 success 会在 resolve 被调用的时候执行", () =>
    new Promise((done) => {
      /* vitest 中的 done 写法:https://cn.vitest.dev/guide/migration.html */
      const success = vi.fn();
      const promise = new Promise((resolve, reject) => {
        expect(success).not.toHaveBeenCalled();
        setTimeout(() => {
          resolve();
        }, 10);
      });
      expect(promise.state).toMatchInlineSnapshot('"pending"');
      setTimeout(() => {
        expect(success).toHaveBeenCalled();
        expect(promise.state).toMatchInlineSnapshot('"fulfilled"');
        done();
      }, 20);
      promise.then(success);
    }));

  it("4.2 promise.then(null,fail) 中的 fail 会在 reject 被调用的时候执行", () =>
    new Promise((done) => {
      const fail = vi.fn();
      const promise = new Promise((resolve, reject) => {
        expect(fail).not.toHaveBeenCalled();
        setTimeout(() => {
          reject();
        }, 10);
      });
      expect(promise.state).toMatchInlineSnapshot('"pending"');
      setTimeout(() => {
        expect(fail).toHaveBeenCalled();
        expect(promise.state).toMatchInlineSnapshot('"rejected"');
        done();
      }, 20);
      promise.then(null, fail);
    }));
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

通过上面的测试案例可知,.then 中的函数会被 resolve 或者 reject 调用。并且

  • .then 是同步执行代码。
  • .resolve 有可能被包裹在一个异步执行环境中,至于什么时候触发不清楚。

因此这里需要做的就是:先将 .then 中的结果缓存起来,在resolve 调用的时候再从变量中取出即可。

class PromiseCore {
  state: "pending" | "fulfilled" | "rejected" = "pending";
  succcessCallback: Function = undefined;
  failCallback: Function = undefined;
  constructor(fn: Function) {
    if (typeof fn !== "function") {
      throw new Error("请输入函数");
    }
    fn(this.resolve.bind(this), this.reject.bind(this));
  }

  /* 原形方法 */
  resolve() {
    if (this.state !== "pending") return;
    this.state = "fulfilled";
    /* @todo: 这里先用 setTimeout 触发宏任务过程 */
    setTimeout(() => {
      this.successCallback();
    });
  }
  reject() {
    if (this.state !== "pending") return;
    this.state = "rejected";
    /* @todo: 这里先用 setTimeout 触发宏任务过程 */
    setTimeout(() => {
      this.failCallback();
    });
  }

  /* then 的核心就是将对应的函数缓存在 successCallback 或者 failCallback 函数中 */
  then(succeed?: CallBackType, fail?: CallBackType) {
    if (typeof succeed === "function") {
      successCallback = succeed;
    }
    if (typeof fail === "function") {
      failCallback = fail;
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

这里有可能会有一个问题:resolve 或 reject 为啥在触发的时候需要通过 setTimeout 包裹一层呢?

主要是考虑到下面的案例:

new Promise((resolve, reject) => {
  resolve("成功");
}).then((x) => {
  console.log(x);
});
1
2
3
4
5

这里 resolve 并不是在一个异步环境中,但是执行逻辑是:

  1. 将 (x) => console.log(x) 保存到 successCallback 函数到上。
  2. 这样以后才可以在 resolve 触发时获取到 successCallback 不会为 undefined 。

这里 setTimeout 的目的就是为了等一下 then 函数的执行,调换resolve 和 then 函数实际执行次序,现在这里先用简单的 setTimeout 宏任务处理,后续会替换的。

自此,一个非常简易的版本的 Promise 已经实现了,是不是不敢相信,Promise 的实现是如此的简单。虽然这种实现离一个可用的符合 PromiseA+ 的 Promise 还差的很远。但是至少实现一个类状态控制器。进一步思考,是不是可以将这种编程范式扩展至其余的所需具备状态控制的领域呢?

# 2. 支持链式调用及结果传递

从本节开始完善 Promise,尽量往 PromiseA+ 规范上靠。

上述已实现状态控制,但是缺少如下核心功能:

  1. promise.then 支持多次回调存储。
  2. .then 返回的是一个新的 promise (状态为 pending)
  3. promise(resolve => resolve("xxx")).then(x => console.log(x)) 支持将结果传递出去。

由于其实现过程还是较为复杂的,先从支持多次回调存储功能出发:

it("【中】2.2.6 支持链式 then 的写法,要求调用顺序遵循书写顺序", () =>
  new Promise((done) => {
    const promise = new Promise((resolve) => {
      resolve();
    });

    const callbacks = [vi.fn(), vi.fn(), vi.fn()];
    promise.then(callbacks[0]);
    promise.then(callbacks[1]);
    promise.then(callbacks[2]);
    setTimeout(() => {
      expect(callbacks[0]).toHaveBeenCalled();
      expect(callbacks[1]).toHaveBeenCalled();
      expect(callbacks[2]).toHaveBeenCalled();

      /* ChatGPT 给出的方案:如下结果分别为 [7][8][9] */
      console.log(callbacks[0].mock.invocationCallOrder);
      console.log(callbacks[1].mock.invocationCallOrder);
      console.log(callbacks[2].mock.invocationCallOrder);

      /* 0 < 1  */
      expect(callbacks[0].mock.invocationCallOrder[0]).toBeLessThan(
        callbacks[1].mock.invocationCallOrder[0],
      );

      /* 1 < 2  */
      expect(callbacks[1].mock.invocationCallOrder[0]).toBeLessThan(
        callbacks[2].mock.invocationCallOrder[0],
      );
      done();
    });
  }));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

如果希望 .then 支持多次回调的话,原先的变量 successCallback 或者 failCallback 就显得不够用了,需要升级为回调数组,resolve() 时遍历 callback 数组。

class PromiseCore {
  /* 其中 callbacks 对应如下:
  	 successCallBack <= callback[0]
  	 failCallBack <= callback[1]
  	 .then 的回调 <= callback[2] 【暂未实现:后续将 nextPromise 缓存在此】
  */
  callbacks: [CallBackType, CallBackType, myPromise][] = [];
  /* 原型方法 */
  resolve(result) {
    if (this.state !== "pending") return;
    this.state = "fulfilled";
    nextTick(() => {
      this.callbacks.forEach((handle) => {
        const success = handle[0];
        if (typeof success === "function") {
          const x = success(result);
        }
      });
    });
  }

  reject(reason) {
    if (this.state !== "pending") return;
    this.state = "rejected";
    nextTick(() => {
      this.callbacks.forEach((handle) => {
        const fail = handle[1];
        if (typeof fail === "function") {
          fail(reason);
        }
      });
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

第二条规则:.then 返回的是一个 Promise,且状态为 pending

如果是一般的类函数实现链式调用的话,有个技巧是直接返回自身即可,但这种做法在此案例中并不适用。原因在于要求返回的类状态必须有要求,为 pending 状态。

因此 .then 的返回结果是一个全新具有 pending 状态的 Promise,代码示例如下:

class PromiseCore {
  then(succeed?: CallBackType, fail?: CallBackType) {
    const handle = [] as unknown as [CallBackType, CallBackType, any];
    if (typeof succeed === "function") {
      handle[0] = succeed;
    }
    if (typeof fail === "function") {
      handle[1] = fail;
    }
    /* 错误的写法 */
    - return this;
    /* 返回一个新的 Promise  */
    + return new PromiseCore(() => {});
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

第三条规则:将结果参数进行链式传递,我认为这条规则是 Promise 编码中最难实现的规则之一。

其实它的 MVP 实现也是可以通过简单的临时变量实现的,构造 nextPromise 变量,在 then 函数返回时对该变量赋值。当 resolve 时,将结果传递给 nextPromise.resolve() 函数中。

class PromiseCore {
	+ nextPromise = undefined;
  then(success?,fail?){
    .....
    /*  1. 将 新的 Promise 缓存起来 */
    + nextPromise = new PromiseCore(()=>{});
		return nextPromise;
  }

  resolve(result){
    if (this.state !== "pending") return;
    this.state = "fulfilled";
    setTimeout(() => {
      this.callbacks.forEach((handle) => {
        const success = handle[0];
        if (typeof success === "function") {
          /* 2. 缓存 x 计算的结果 */
          const x = success(result);
          /* 3. 将计算的结果传递给 nextPtomise.resolve(x) */
          + nextPromise.resolve(x);
        }
      });
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

PS:为了后续编码考虑,可将 nextPromise 作为 callback 的第三个参数一起缓存。

自此,Promise 的核心功能已基本实现,如果用户不做什么骚操作的话是大致是没问题的,但是作为函数库来说,必须对所有可能犯的错进行避免,提升函数鲁棒性。

# 3. 注意:函数库调用时设置严格模式

通过上面的案例,我们已经掌握 Promise 中的一个核心逻辑是 resolve 触发的是在 .then() 中缓存的函数。函数触发时可以通过 this 访问到 Promise 实例,这一点最好是被限制住,始终保证为严格模式会比较好。

测试案例:

注:以下在 .then 中是 function 写法,如果是箭头函数就不存在这个问题了 。

it("【难】2.2.5 onFulfilled和onRejected 被当做函数调用时,this 指向 undefined", () => {
  const promise = new Promise((resolve) => {
    resolve();
  });

  /* function 写法 */

  promise.then(function() {
    /* 注意此时应为严格模式 */
    console.log("this", this);
    expect(this).toBeUndefined();
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13

希望测试的是 .then 中访问 this 指针时需要重置为 undefined 保证严格模式。

实现时,只需要将 resolve 或者 reject 触发时需要通过 bind 重新指向 undefined 即可。

class PromiseCore {
  /* 原型方法 */
  resolve(result) {
    if (this.state !== "pending") return;
    this.state = "fulfilled";
    nextTick(() => {
      this.callbacks.forEach((handle) => {
        const success = handle[0];
        if (typeof success === "function") {
          - const x = success(result);
          + const x = success.call(undefined, result);
          handle[2].resolveWith(x);
        }
      });
    });
  }

  reject(reason) {
    if (this.state !== "pending") return;
    this.state = "rejected";
    nextTick(() => {
      this.callbacks.forEach((handle) => {
        const fail = handle[1];
        if (typeof fail === "function") {
          - fail(reason);
          + fail.call(undefined, reason);
        }
      });
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 4. 处理异常回调结果

这部分主要对应于 2.2.7.1 规则,前面的 resolve() 仅支持普通类型的数据,通过新增 resolveWith 处理更多的参数类型。

将原先的参数回调改造如下:

class PromiseCore {
  /* 使用 function 定义规则 */
  + resolveWith(){
  +  // 此处 this 指向 nextPromise
  + }

  /* 原型方法 */
  resolve(result) {
    if (this.state !== "pending") return;
    this.state = "fulfilled";
    nextTick(() => {
      this.callbacks.forEach((handle) => {
        const success = handle[0];
        if (typeof success === "function") {
          /* 保证调用时为严格模式 */
          const x = success.call(undefined, result);
          - nextPromise.resolve(x);
          + nextPromise.resolveWith(x);
        }
      });
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

基于 PromiseA+ 的规则约定如下:

  1. 如果 resolve 接受的是 nextPromise 自身,会引起循环处理,需要将此错误 reject。
  2. 如果 resolve 接受的是一个 Promise 实例,则使用 then 取出结果后透传给 resovle 或者 reject 参数。
  3. 如果 resolve 接受的是一个 thenable 对象,和上述的 Promise 不同之处在于,Promise.then() 取出的一定是个普通类型的结果,而 thenable.then() 有可能仍是一个 Promise/thenable/普通数据类型。因此需要使用 resolveWith() 进行递归处理。
  4. 如果 resolve 接受的是一个普通类型,就直接调用 resolve(res) 函数 。
it("2.2.7.1.2 success 的返回值是一个 Promise 实例", () => {
  const promise1 = new Promise((resolve) => resolve());

  /* .then 中返回的是一个 Promise 类型*/
  const promise2 = promise1.then(
    () => new Promise((resolve) => resolve("成功")),
  );

  /* 此时 x 为 new Promise((resolve) => resolve("成功")
       直接 x.then 后取出结果,并传递给 resolveWith 函数中的 this.resolve() 即可。
    */
  promise2.then((res) => {
    console.log("res", res);
    expect(res).toMatchInlineSnapshot('"成功"');
  });
});

it("【需非常注意】2.2.7.1.2 success 的返回值是一个 thenable 对象, 成功调用", () => {
  const promise1 = new Promise((resolve) => resolve());

  /* .then 中返回的是一个 Promise 类型*/
  const promise2 = promise1.then(() => {
    return {
      then: (resolve, reject) => {
        resolve("成功");
      },
    };
  });

  promise2.then((res) => {
    console.log("res", res);
    expect(res).toMatchInlineSnapshot('"成功"');
  });
});

it("【需非常注意】2.2.7.1.2 success 的返回值是一个 thenable 对象, 失败调用", () => {
  const promise1 = new Promise((resolve) => resolve());
  /* .then 中返回的是一个 Promise 类型*/
  const promise2 = promise1.then(() => {
    return {
      then: (resolve, reject) => {
        reject("失败");
      },
    };
  });

  promise2.then(null, (reason) => {
    console.log("res", reason);
    expect(reason).toMatchInlineSnapshot('"失败"');
  });
});

it("【需非常注意】2.2.7.1.2 success 的返回值不能是 promise 自身索引", () =>
  new Promise((done) => {
    const promise1 = new Promise((resolve) => resolve());
    /* 使用 promise1 的 then 返回一个新引用地址的 promise2 */
    /* 不允许处理自身的引用地址 */
    const promise2 = promise1.then(() => promise2);
    promise2.then(null, (reason) => {
      expect(reason).toMatchInlineSnapshot("[TypeError: 不允许返回自身引用]");
      done();
    });
  }));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

对应的实现如下:

class PromiseCore {
  /* 此时为 Promise2 环境 */
  resolveWith(x) {
    /* 如果 then 返回的是引用自身,则报错 */
    if (this === x) {
      this.reject(new TypeError("不允许返回自身引用"));
    } else if (x instanceof PromiseCore) {
      /* 如果是一个 Promise, 需要通过 then 获取值 */
      x.then(
        (result) => {
          this.resolve(result);
        },
        (reason) => {
          this.reject(reason);
        },
      );
    } else if (x instanceof Object) {
      /* 如果 x 是 thenable */
      let then;
      try {
        then = x.then;
      } catch (error) {
        this.reject(error);
      }
      if (then instanceof Function) {
        try {
          then(
            (y) => {
              /* 特别注意:x 是 thenable 和 promise 最大的不同是,x.then 的回调结果
                前者执行后还有可能是 自身引用/promise/thenable/普通类型; 而后者只有可能是普通类型。
                因此这里千万注意要使用  this.resolveWith(y)
              */
              this.resolveWith(y);
            },
            (r) => {
              this.reject(r);
            },
          );
        } catch (error) {
          this.reject(error);
        }
      }
    } else {
      /* 如果 x 是一个普通类型 */
      /* 通过 this.resolve 将结果传递给 then 后的回调结果 */
      this.resolve(x);
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 5.微任务 PolyFill 方案

此前代码都是使用 setTimeout 将 then 和 resolve/reject 执行次序颠倒,但是这对于代码中存在微任务代码时可能会出现一些问题。

因此需要保证 PromiseCore 改造为微任务,对于不同的环境微任务的处理方案有所不同:

  1. 对于 Node 执行环境,直接使用 process.nextTick() 即可。
  2. 对于 browser 执行环境,使用 MutationObserver 实现 Polyfill 功能。
    • 创建一个虚拟的节点:TextNode
    • 开启 Observer 监听节点的改变。
    • 改变状态 TextNode.data = count + 1
/* 添加 polyfill */
function nextTick(fn) {
  /* Node 环境下使用 process.nextTick 模拟 */
  /* @ts-ignore */
  if (process !== undefined && typeof process.nextTick === "function") {
    /* @ts-ignore */
    return process.nextTick(fn);
  }

  /* Vue源码:浏览器环境使用 MutationObserver */
  let counter = 1;
  const observer = new MutationObserver(fn);
  /* 1. 创建一个虚拟的文本节点 */
  const textNode = document.createTextNode(counter + "");

  /* 2. 开启 Mutation 去监听 TextNode 这个 DOM 元素 */
  observer.observe(textNode, {
    characterData: true,
  });

  /* 3. 触发 TextNode 数据属性的改变 */
  counter += 1;
  textNode.data = counter + "";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

通过查阅 【W3C 官网】 (opens new window) 可知 MutationObserver 是一个微任务。

image-20230506153056259

如果场景不是封装一个 Promise 中的 nextTick ,可以进一步更为通用的微任务方法:

function microTask(func) {
  // 1. 有 Promise 就用 Promise 创建的 microTask
  if (typeof Promise !== "undefined") {
    Promise.resolve().then(func);
  } else if (typeof MutationObserver !== "undefined") {
    // 2.没有 Promise, 就看是否存在 MutationObserver
    const ob = new MutationObserver(func);
    const textNode = document.createTextNode("0");
    ob.server(textNode, {
      characterData: true,
    });
    textNode.data = "1";
  } else {
    // 3.最后,使用 setTimeout 兜底。
    setTimeout(func);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 6. 各种静态函数实现

上述已经实现了 PromiseCore 类基础功能,可以在此基础上封装便捷的工具函数。

  • Promise.resolve() 创建一个 fulfilled 状态的 Promise
  • Promise.reject() 创建一个 rejected 状态的 Promise
  • Promise.all 并发执行 Promise 列表,缺点是不允许支持失败。
  • Promise.allSettled 并发执行 Promise 列表,弥补 all 不允许失败的问题。
  • Promise.race 竞速函数,捕获第一次返回结果无论 fulfilled 或者 rejected 状态。
  • Promise.any 竞速函数 2.0 版本,只捕获成功返回,当所有都错误时才返回错误。
  • Promise.finally 虽然用的比较少,但此函数的作用一举打破了 promise 必须有明确状态的时才允许数据流通。一般可以将 .then 和 .catch 公共的部分提取出来写到此函数中。finally 本身不会对类状态做任何改变,即使返回状态也不会透传下去。

# 6.1 resolve[有些问题]

测试案例:

import { describe, expect, it, vi } from "vitest";
import myPromise from "./myPromise";

describe("测试 Promise", () => {
  it("测试 Promise.resolve 接受普通类型 ", () =>
    new Promise<void>((done) => {
      myPromise.resolve("成功").then((res) => {
        expect(res).toMatchInlineSnapshot('"成功"');
        done();
      });
    }));

  it("测试 Promise.resolve 接受 Promise ", () =>
    new Promise<void>((done) => {
      const promise = new myPromise((resolve) => resolve("成功"));
      myPromise.resolve(promise).then((res) => {
        expect(res).toMatchInlineSnapshot('"成功"');
        done();
      });
    }));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

功能代码:

import { PromiseCore } from "./promiseCore";
PromiseCore.resolve = (parameter) => {
  /* 注:resolve 支持接受 promise */
  if (parameter instanceof PromiseCore) {
    return parameter;
  }
  /* 否则构造一个 new Promise 返回 
     由 PromiseCore 处理 自引用|thenable|普通类型等返回值类型
  */
  return new PromiseCore((resolve) => resolve(parameter));
};
1
2
3
4
5
6
7
8
9
10
11

# 6.2 reject

测试案例:

it("测试 Promise.reject 接受错误 ", () =>
  new Promise<void>((done) => {
    const promise = new myPromise((resolve, reject) => reject("失败原因"));
    myPromise.resolve(promise).then(null, (reason) => {
      expect(reason).toMatchInlineSnapshot('"失败原因"');
      done();
    });
  }));
1
2
3
4
5
6
7
8

功能代码:

PromiseCore.reject = (parameter) => {
  /* 构造一个新的 Promise 并返回 */
  return new PromiseCore((resolve, reject) => reject(parameter));
};
1
2
3
4

# 6.3 all

测试案例:

it("测试 Promise.all 功能 -- 成功输入空数组", () =>
  new Promise<void>((done) => {
    myPromise.all([]).then((res) => {
      expect(res).toMatchInlineSnapshot("[]");
      done();
    });
  }));
it("测试 Promise.all 功能 -- 成功", () =>
  new Promise<void>((done) => {
    const p1 = 1;
    const p2 = new myPromise((resolve) => resolve(2));
    const p3 = myPromise.resolve(3);
    const p4 = myPromise.reject("err4");

    myPromise.all([p1, p2, p3]).then((res) => {
      expect(res).toMatchInlineSnapshot(`
          [
            1,
            2,
            3,
          ]
        `);
      done();
    });
  }));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

功能代码:

PromiseCore.all = (promiseList: any[]) => {
  /* 记录结果数组 */
  const result = [] as any[];
  let resCount = 0;
  return new PromiseCore((resolve, reject) => {
    if (promiseList.length === 0) {
      resolve([]);
    }
    promiseList.forEach((p, i) => {
      PromiseCore.resolve(p).then(
        (res) => {
          result[i] = res;
          resCount++;
          if (resCount === promiseList.length) {
            resolve(result);
          }
        },
        (reason) => {
          reject(reason);
        },
      );
    });
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 6.4 allSettled

测试代码:

it("测试 Promise.allSettled 功能", () =>
  new Promise<void>((done) => {
    const p1 = 1;
    const p2 = new myPromise((resolve) => resolve(2));
    const p3 = myPromise.resolve(3);
    const p4 = myPromise.reject("err4");

    myPromise.allSettled([p1, p2, p4]).then((res) => {
      expect(res).toMatchInlineSnapshot(`
          [
            {
              "status": "fulfilled",
              "value": 1,
            },
            {
              "status": "fulfilled",
              "value": 2,
            },
            {
              "reason": "err4",
              "status": "rejected",
            },
          ]
        `);
      done();
    });
  }));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

功能代码:

PromiseCore.allSettled = (promiseList: any[]) => {
  const result = [] as any[];
  let resovleCount = 0;
  return new PromiseCore((resolve) => {
    if (promiseList.length === 0) {
      resolve([]);
    }
    promiseList.forEach((p, i) => {
      PromiseCore.resolve(p).then(
        (res) => {
          resovleCount++;
          result[i] = {
            status: "fulfilled",
            value: res,
          };
          if (resovleCount === promiseList.length) {
            return resolve(result);
          }
        },
        (reason) => {
          resovleCount++;
          result[i] = {
            status: "rejected",
            reason: reason,
          };
          if (resovleCount === promiseList.length) {
            return resolve(result);
          }
        },
      );
    });
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 6.5 race

测试代码:

it("测试 Promise.race 功能 - 第一个返回成功", () =>
  new Promise<void>((done) => {
    const p1 = new myPromise((reject) => {
      setTimeout(() => reject(200), 200);
    });
    const p2 = new myPromise((resolve) => {
      setTimeout(() => resolve(100), 100);
    });

    myPromise.race([p1, p2]).then((res) => {
      expect(res).toMatchInlineSnapshot("100");
      done();
    });
  }));
it("测试 Promise.race 功能 - 第一个返回失败", () =>
  new Promise<void>((done) => {
    const p1 = new myPromise((resolve) => {
      setTimeout(() => resolve(200), 200);
    });
    const p2 = new myPromise((resolve, reject) => {
      setTimeout(() => reject(100), 100);
    });

    myPromise.race([p1, p2]).catch((reason) => {
      expect(reason).toMatchInlineSnapshot("100");
      done();
    });
  }));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

功能代码:

PromiseCore.race = (promiseList: any[]) => {
  return new PromiseCore((resolve, reject) => {
    if (promiseList.length === 0) {
      resolve([]);
    }
    promiseList.forEach((p) => {
      PromiseCore.resolve(p).then(
        (res) => resolve(res),
        (reason) => reject(reason),
      );
    });
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13

# 6.6 any

测试代码:

it("测试 Promise.any 功能 - 失败fast,成功slow", () =>
  new Promise<void>((done) => {
    const p1 = new myPromise((resolve) => {
      setTimeout(resolve, 50, "成功-slow");
    });
    const p2 = new myPromise((resolve, reject) => {
      setTimeout(reject, 10, "失败-quick");
    });

    myPromise.any([p1, p2]).then((res) => {
      expect(res).toMatchInlineSnapshot('"成功-slow"');
      done();
    });
  }));
it("测试 Promise.any 功能 - 均失败", () =>
  new Promise<void>((done) => {
    const p1 = myPromise.reject("err1");
    const p2 = myPromise.reject("err2");

    myPromise.any([p1, p2]).catch((reason) => {
      expect(reason).toMatchInlineSnapshot('"All Promise task failed"');
      done();
    });
  }));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

功能代码:

PromiseCore.any = (promiseList: any[]) => {
  let rejectCount = 0;
  return new PromiseCore((resolve, reject) => {
    if (promiseList.length === 0) {
      resolve([]);
    }
    promiseList.forEach((p) => {
      PromiseCore.resolve(p).then(
        (res) => resolve(res),
        (reason) => {
          rejectCount++;
          if (rejectCount === promiseList.length) {
            reject("All Promise task failed");
          }
        },
      );
    });
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 6.7 finally

测试代码:

it("测试 Promise.finally 功能 - 继承 成功 状态", () =>
  new Promise<void>((done) => {
    new myPromise((resolve, reject) => {
      resolve("success");
    })
      .finally(
        () =>
          new myPromise((resolve) => {
            setTimeout(() => {
              resolve(111);
            }, 10);
          }),
      )
      .then((data) => {
        expect(data).toMatchInlineSnapshot('"success"');
        done();
      });
  }));
it("测试 Promise.finally 功能 - 继承 失败 状态", () =>
  new Promise<void>((done) => {
    new myPromise((resolve, reject) => {
      reject("error1");
    })
      .finally(
        () =>
          new myPromise((resolve) => {
            setTimeout(() => {
              resolve(111);
            }, 10);
          }),
      )
      .then(null, (reason) => {
        expect(reason).toMatchInlineSnapshot('"error1"');
        done();
      });
  }));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

功能代码:

/* 绑定在原型上,所有实例都将继承 finally 属性 */
PromiseCore.prototype.finally = function(cb) {
  /* Promise.finally 将透传 Promise 的状态 !!! */
  return new Promise((resolve, reject) => {
    /* 继承 this 的结果 */
    this.then(
      (value) => PromiseCore.resolve(cb()).then(() => resolve(value)),
      (reason) => PromiseCore.resolve(cb()).then(() => reject(reason)),
    );
  });
};
1
2
3
4
5
6
7
8
9
10
11
编辑 (opens new window)
上次更新: 2023/10/02, 17:10:00
30.Promise系列-目录
32.Promise-指令式Promise改造

← 30.Promise系列-目录 32.Promise-指令式Promise改造→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式