22.函数技巧-tapable钩子
# 0. 前言
前一篇文章可知,传统的 compose
存在一定的问题,但是整个函数式编程范式非常有效,作为函数组合器中,webpack
的 tapable
库给了一个非常好的范式。
# 1. tapable
钩子分类
Sync
同步版本 compose
函数
SyncHook
:串行同步执行,不进行返回值传递。SyncBailHook
:串行同步执行,Bail 保险丝模式,只要返回的不是null
,则后续函数执行。SyncWaterfallHook
:串行同步执行,支持返回值传递。SyncLoopHook
:串行同步执行,只有显式返回true
,代表继续执行后续函数;返回undefined
则不再后续执行。除此以外,循环执行当前函数。
Async
异步版本 compose
函数
对于异步版本需区分出:并行和串行模式。
AsyncParalHook
:异步并行触发,不关心返回值。AsyncSeriesHooks
:异步串行触发。AsyncSeriesHooks
:可中断的异步函数链。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");
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");
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");
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");
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");
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");
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");
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");
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");
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"));
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("串行执行失败");
},
);
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());
});
};
};
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("串行执行失败");
},
);
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());
});
};
};
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());
});
};
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21