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钩子
      • 0. 前言
      • 1. tapable 钩子分类
      • 2. Sync* 型 Hook
        • 2.1 SyncHook
        • 2.2 SyncBailHook
        • 2.3 SyncWaterFallHook
        • 2.4 SyncLoopHook
      • 3 Async* 型Hook
        • 3.1 AsyncParallelHook
        • 3.2 AsyncSeriesHook
        • 3.3 SyncSeriesWaterFallHook
    • 23.函数技巧-koa源码
    • 30.Promise系列-目录
    • 31.Promise-深入Promise原理
    • 32.Promise-指令式Promise改造
    • 32.promise-梳理 Promise 错误处理
    • 34.promise-竞态问题
    • 35.Promise-如何处理异步数组
  • 深入浅出 Vite

  • 快速上手 API

  • 深入浅出Babel

  • 深入浅出 React

  • 百问掘金
  • 编码专题
wangjiasheng
2023-10-02
目录

22.函数技巧-tapable钩子

# 0. 前言

前一篇文章可知,传统的 compose 存在一定的问题,但是整个函数式编程范式非常有效,作为函数组合器中,webpack 的 tapable 库给了一个非常好的范式。

# 1. tapable 钩子分类

Sync 同步版本 compose 函数

  1. SyncHook:串行同步执行,不进行返回值传递。
  2. SyncBailHook:串行同步执行,Bail 保险丝模式,只要返回的不是 null,则后续函数执行。
  3. SyncWaterfallHook:串行同步执行,支持返回值传递。
  4. SyncLoopHook :串行同步执行,只有显式返回 true,代表继续执行后续函数;返回 undefined 则不再后续执行。除此以外,循环执行当前函数。

Async 异步版本 compose 函数

对于异步版本需区分出:并行和串行模式。

  1. AsyncParalHook:异步并行触发,不关心返回值。

  2. AsyncSeriesHooks:异步串行触发。

  3. AsyncSeriesHooks:可中断的异步函数链。

  4. AsyncSeriesWaterfallHook 异步串行值传递。

# 2. Sync* 型 Hook

# 2.1 SyncHook

串行同步执行,不关心返回值

可执行地址:https://wangjs-jacky.github.io/Learn-react-code/code/02-tapable/01-sync

函数版本

const a = (arg) => console.log(1, arg);
const b = (arg) => console.log(2, arg);
const c = (arg) => console.log(3, arg);

/* 函数式写法
   执行次序: a() -> b() -> c();
*/
function compose(...fns) {
  return (...args) => fns.forEach((task) => task(...args));
}

compose(a, b, c)("Hello");
1
2
3
4
5
6
7
8
9
10
11
12

类版本

class SyncHook {
  /* 初始化 */
  constructor(name) {
    this.tasks = [];
    this.name = name;
  }
  tap(task) {
    this.tasks.push(task);
  }

  /* 支持传入公共参数 */
  call() {
    this.tasks.forEach((task) => task(...arguments));
  }
}

let queue = new SyncHook("name");

/* 使用 tap 存入数组 */
queue.tap(a);
queue.tap(b);
queue.tap(c);

queue.call("hello-2");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 2.2 SyncBailHook

串行同步执行,保险丝模式,有返回值代表 error

可执行地址:https://wangjs-jacky.github.io/Learn-react-code/code/02-tapable/02-sync-bail

函数版本

/* Bail - 保险丝 */
const a = (arg) => console.log(1, arg);
const b = (arg) => "123";
const c = (arg) => console.log(3, arg);

/*  
  当 fn() 返回值不为 null 时,停止循环
*/

const compose = (...fns) => {
  let err = null;
  return (...args) => {
    for (let i = 0; i < fns.length; i++) {
      err = fns[i](...args);
      if (err) {
        err = null;
        return;
      }
    }
  };
};

compose(a, b, c)("Hello");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

类版本

class SyncBailHook {
  constructor(name) {
    this.tasks = [];
    this.name = name;
  }
  tap(task) {
    this.tasks.push(task);
  }
  call() {
    let i = 0,
      ret;
    do {
      ret = this.tasks[i++](...arguments);
    } while (!ret);
  }
}

let queue = new SyncBailHook("name");

queue.tap(a);
queue.tap(b);
queue.tap(c);

queue.call("Hello-2");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 2.3 SyncWaterFallHook

同步串行执行,支持值传递。

可执行地址:https://wangjs-jacky.github.io/Learn-react-code/code/02-tapable/03-sync-waterfall

函数版本

/* WaterFall 瀑布模式 */

const a = (initArg) => {
  console.log("initArg", initArg);
  return "a";
};
const b = (arg) => {
  console.log("arg", arg);
  return "b";
};
const c = (arg) => {
  console.log("arg", arg);
};

const compose = (...fns) => {
  return (...args) => {
    const [first, ...otherFns] = fns;
    otherFns.reduce((ret, cur) => cur(ret), first(...args));
  };
};

compose(a, b, c)("Hello");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

类版本

class SyncWaterFallHook {
  constructor(name) {
    this.tasks = [];
    this.name = name;
  }
  tap(task) {
    this.tasks.push(task);
  }
  call() {
    const [first, ...otherFns] = this.tasks;
    otherFns.reduce((ret, cur) => cur(ret), first(...arguments));
  }
}

let queue = new SyncWaterFallHook("name");

queue.tap(a);
queue.tap(b);
queue.tap(c);

queue.call("Hello-2");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 2.4 SyncLoopHook

串行同步,当返回 true 执行后续函数;当返回 undefined 停止执行

可执行地址:https://wangjs-jacky.github.io/Learn-react-code/code/02-tapable/04-sync-loop

函数版本

/* Loop - 循环执行任务*/

const a = (arg) => {
  console.log("arg", arg);
  return undefined;
};

function generateB() {
  let count = 0;
  return (arg) => {
    if (count !== 3) {
      count++;
      console.log("b", arg);
      return "b";
    } else {
      return undefined;
    }
  };
}

const c = (arg) => {
  console.log("c", arg);
  return undefined;
};

/*  
  当 fn() 返回值不为 undefined 时,不爆错,但是需要继续循环
*/

const compose = (...fns) => {
  return (...args) => {
    fns.forEach((task) => {
      let ret = true;
      do {
        ret = task(...args);
      } while (ret === true || !(ret === undefined));
    });
  };
};

compose(a, generateB(), c)("Hello");
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

类版本

class SyncLoopHook {
  constructor(name) {
    this.tasks = [];
    this.name = name;
  }
  tap(task) {
    this.tasks.push(task);
  }
  call(...args) {
    this.tasks.forEach((task) => {
      let ret = true;
      do {
        ret = task(...args);
      } while (ret === true || !(ret === undefined));
    });
  }
}

let queue = new SyncLoopHook("name");

queue.tap(a);
queue.tap(generateB());
queue.tap(c);

queue.call("Hello-2");
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

# 3 Async* 型Hook

# 3.1 AsyncParallelHook

异步并行,没有顺序触发要求,简单使用 Promise.all 触发即可

可执行地址:https://wangjs-jacky.github.io/Learn-react-code/code/02-tapable/05-aync-parallel

函数版本

class SyncLoopHook {
  constructor(name) {
    this.tasks = [];
    this.name = name;
  }
  tap(task) {
    this.tasks.push(task);
  }
  call(...args) {
    this.tasks.forEach((task) => {
      let ret = true;
      do {
        ret = task(...args);
      } while (ret === true || !(ret === undefined));
    });
  }
}

let queue = new SyncLoopHook("name");

queue.tap(a);
queue.tap(generateB());
queue.tap(c);

queue.call("Hello-2");
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

类版本

class AsyncParallelHook {
  constructor(name) {
    this.tasks = [];
    this.name = name;
  }
  tapPromise(task) {
    this.tasks.push(task);
  }

  promise() {
    let promises = this.tasks.map((task) => task());
    // Promise.all所有的Promsie执行完成会调用回调
    return Promise.all(promises);
  }
}

let queue = new AsyncParallelHook("name");

queue.tapPromise(a);
queue.tapPromise(b);
queue.tapPromise(c);

console.time("time2");

queue.promise("Hello-2").then(() => console.timeEnd("time2"));
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

# 3.2 AsyncSeriesHook

异步串行模式,支持打断行为,当返回的是 rejected 状态的 Promise 时,需停止后续执行

可执行地址:https://wangjs-jacky.github.io/Learn-react-code/code/02-tapable/07-async-series-waterfall

函数版本

使用 reduce 构建 promise 串

/* 异步串行-支持打断 */
const a = function(name) {
  return new Promise<void>(function(resolve, reject) {
    setTimeout(function() {
      console.log("a");
      resolve();
    }, 100);
  });
};

const generateB = (type: string) => (name) => {
  return new Promise<void>(function(resolve, reject) {
    setTimeout(function() {
      console.log("b");
      /* reject(); */
      if (type === "success") {
        resolve();
      } else if (type === "fail") {
        reject();
      }
    }, 100);
  });
};

const b_success = generateB("success");
const b_fail = generateB("fail");

const c = function(name) {
  return new Promise<void>(function(resolve, reject) {
    setTimeout(function() {
      console.log("c");
      resolve();
    }, 100);
  });
};

/* 串行: 简单模式
a().then(()=> b()).then(()=>c())
  .then(()=>console.log("串执行结束"))
  .catch(()=> console.log("串行执行报错"))
*/
const compose = (...fns) => {
  return (...args) => {
    const [first, ...otherFns] = fns;
    return new Promise<void>((resolve, reject) => {
      otherFns
        .reduce((pre, cur) => {
          return pre.then(() => {
            return cur(...args);
          });
        }, first(...args) /* args 传给第一个函数 */)
        .then(() => resolve())
        .catch(() => reject());
    });
  };
};

console.time("time1");
compose(
  a,
  b_success,
  c,
)("name").then(
  () => {
    console.timeEnd("time1");
  },
  () => {
    console.log("串行执行失败");
  },
);
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
64
65
66
67
68
69
70

函数版本

使用 for 循环 + async + await 实现

/* 使用 async 和 await 实现 */
const compose2 = (...fns) => {
  return (...args) => {
    return new Promise<void>((resolve, reject) => {
      async function main() {
        for (let i = 0; i < fns.length; i++) {
          try {
            /* 保证每次执行成功 */
            await fns[i](args);
          } catch (error) {
            /* 直接弹出 */
            reject();
            /* 不再执行后续流程 */
            return;
          }
        }
      }
      main().then(() => resolve());
    });
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

类版本

class AsyncSeriesHook {
  constructor(name) {
    this.tasks = [];
    this.name = name;
  }
  tapPromise(task) {
    this.tasks.push(task);
  }

  promise() {
    return new Promise<void>((resolve, reject) => {
      const main = async () => {
        for (let i = 0; i < this.tasks.length; i++) {
          try {
            /* 保证每次执行成功 */
            await this.tasks[i]();
          } catch (error) {
            /* 直接弹出 */
            reject();
            /* 不再执行后续流程 */
            return;
          }
        }
      };
      main().then(() => resolve());
    });
  }
}

let queue = new AsyncSeriesHook("name");
queue.tapPromise(a);
queue.tapPromise(b_success);
queue.tapPromise(c);
console.time("time3");
queue.promise().then(
  () => console.timeEnd("time3"),
  () => {
    console.log("串行执行失败");
  },
);
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

# 3.3 SyncSeriesWaterFallHook

异步串行,支持参数传递,只需要将上述函数简单修改即可。

可执行地址:

函数版本

构建 promise 串实现

const compose = (...fns) => {
  return (...args) => {
    const [first, ...otherFns] = fns;
    return new Promise<void>((resolve, reject) => {
      otherFns
        .reduce((pre, cur) => {
          return pre.then((res) => {
            // 新增部分
            return cur(res);
          });
        }, first(...args))
        // 新增部分
        .then((res) => {
          resolve(res);
        })
        .catch(() => reject());
    });
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

函数版本

使用 async 和 await 实现

/* 使用 async 和 await 实现 */
const compose2 = (...fns) => {
  return (...args) => {
    return new Promise<void>((resolve, reject) => {
      async function main() {
        for (let i = 0; i < fns.length; i++) {
          try {
            /* 保证每次执行成功 */
            await fns[i](args);
          } catch (error) {
            /* 直接弹出 */
            reject();
            /* 不再执行后续流程 */
            return;
          }
        }
      }
      main().then(() => resolve());
    });
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
编辑 (opens new window)
上次更新: 2023/10/10, 0:10:00
21.函数技巧 - 函数式编程之 compose 函数
23.函数技巧-koa源码

← 21.函数技巧 - 函数式编程之 compose 函数 23.函数技巧-koa源码→

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