这是 学习源码设计 第二篇,主要是学习源码实现原理和揣摩设计思路,不会深究每个开源库具体的函数实现。
#0 导读
本文学习的版本是2.0.0-beta.10(截止至目前20200420),拉取的tapable开源库master分支,最新一次commit是75fcb00e84922de8cb0ecb9840c59b68c5ced0d3
很早以前就知道webpack本质上是一种事件流的机制,构建的流程是将各个拆件串联起来,而实现这一切的核心就是Tapable,wepack中负责编译的Compiler和负责创建bundles的Compilation都是Tapable的实例。但是最近几天在看Tapable源码发现,除去这些被使用到的重要模块,version-2.0.0版本的实现惊到了我。
先看下整个tapable库的架构。

可以看出整个tapable库围绕着Hook的概念进行展开,有同步、异步串行、异步并行钩子,每一种钩子都是一个继承自Hook的类。
Hook类系列
| Hook名称 | 钩子使用方式 | 钩子作用 |
|---|---|---|
| SyncHook | tap | 同步钩子 |
| SyncBailHook | tap | 同步钩子,只要执行的 handler 有返回值,剩余 handler 不执行 |
| SyncWaterfallHook | tap | 同步钩子,上一个 handler 的返回值作为下一个 handler 的输入值 |
| SyncLoopHook | tap | 同步钩子,只要执行的 handler 有返回值,一直循环执行此 handler |
| AsyncParallelHook | tap, tapAsync, tapPromise | 异步钩子,handler 并行触发 |
| AsyncParallelBailHook | tap, tapAsync, tapPromise | 异步钩子,handler 并行触发,但是跟 handler 内部调用回调函数的逻辑有关 |
| AsyncSeriesHook | tap, tapAsync, tapPromise | 异步钩子,handler 串行触发 |
| AsyncSeriesBailHook | tap, tapAsync, tapPromise | 异步钩子,handler 串行触发,但是跟 handler 内部调用回调函数的逻辑有关 |
| AsyncSeriesLoopHook | tap, tapAsync, tapPromise | 异步钩子,可以触发 handler 循环调用 |
| AsyncSeriesWaterfallHook | tap, tapAsync, tapPromise | 异步钩子,上一个 handler 可以根据内部的回调函数传值给下一个 handler |
Hook工具类
| 名称 | 作用 |
|---|---|
| HookCodeFactory | 未对外导出,编译生成可执行 fn 的工厂类 |
| HookMap | Map 结构,存储多个 Hook 实例 |
| MultiHook | 组合多个 Hook 实例 |
#1 简单使用
既然tapable本质上是事件流,那么应该会有注册事件tap/tapAsync/tapPromise和触发事件call/callAsync/promise,其原理与events存在基本的相同。
const { SyncHook }= require('tapable');
// 实例化SynvHook
const taphook = new SyncHook(['speed', 'speed2'])
// 通过tap注册handler,其功能类似node的 events.on
taphook.tap('event01', (args1, args2) => {console.log("event01: ", args1, args2)})
taphook.tap({name: 'event02', before: 'event01'}, (args1, args2) => {console.log("event02: ", args1, args2)})
taphook.tap({name: 'event03', stage: -1}, (args1, args2) => {console.log("event03: ", args1, args2)})
// 通过call执行handler,其功能类似node的events.emit
taphook.call("user01-speed-params", "user02-speed2-params")
// 打印结果如下
event03: user01-speed-params user02-speed2-params
event03: user01-speed-params user02-speed2-params
event03: user01-speed-params user02-speed2-params
在实例化SyncHook的时候会接受字符串数组,这个字符串数组长度与handler和call方法的参数长度相等,可理解成call传递的参数会逐个传递每个handler进行相关处理。
就像案例中通过实例化一个taphook,最后调用call传入不同学生的跑步速度,通过不同的handler钩子能够输出不同的结果。
打印结果并不是按照先注册先执行的方式,而是在tapable内部维护了一个会记录每一次tap的参数的执行handler的数组。
type Tap = TapOptions & {
name: string; // 标记每个handler的名字
};
type TapOptions = {
before?: string; // 插入到指定的handler之前,这里相当于有一个插入排序的算法
stage?: number; // handler的顺序优先级,默认是0,这个值越小handler执行顺序排在越前
};
在案例中,name为event02的handler在注册的时候,传递了一个对象进去,且在参数当中指定了event02的handler需要在event01之前执行;再看到event03的handler指定了stage为-1(由于内部库在对handlers排序是staging优先级高,且before排序的默认值为0),所以,整个tap的调用handler顺序为'event03', 'event02', 'event01'
#2 探索原理
说完了这个一个案例的输出结果,接下来顺着案例SyncHook来探索下demo的实现原理
探索原理实现的细节流程如下:
- 问题一: 在创建SyncHook钩子对象的时候执行什么工作呢?
- 问题二:调用tap方法后发生了什么事情呢?
- 问题三:接下来看看注册拦截器做了什么事情?
- 问题四:拦截器如何添加进去的?
- 问题五:_insert函数做了什么事情呢?
- 问题六:案例中taphook.call发生了什么事情呢?
- 问题七:compile内部做了什么事情呢?
- 问题八:问题七当中提到的this.header()做了什么事情呢?
- 问题九:问题七中的this.contentWithInterceptors发生了什么事情呢?
- ⑨ + ①:小结:将各个问题小结一下
①问题: 在创建SyncHook钩子对象的时候执行什么工作呢?
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class SyncHookCodeFactory extends HookCodeFactory {
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
const factory = new SyncHookCodeFactory();
const TAP_ASYNC = () => {
throw new Error("tapAsync is not supported on a SyncHook");
};
const TAP_PROMISE = () => {
throw new Error("tapPromise is not supported on a SyncHook");
};
// 编译生成对应的`fn`, 然后通过调用call方法执行生成的`fn`
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
// 核心,继承Hoo基类,并通过子类实现tapAsync、tapPromise、compile来自定义handler的注册方式
function SyncHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = SyncHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
hook.compile = COMPILE;
return hook;
}
SyncHook.prototype = null;
可以看出SyncHook继承自父类Hook,并且重写了tapAsync、tapPromise、compile三个方法,从SyncHook就能发现SyncHook不支持``tapAsync、tapPromise来注册异步事件流。再说compile方法是用来编译生成对应的fn, 然后通过调用call方法执行生成的fn`。
class Hook {
constructor(args = [], name = undefined) {
// 参数必须是数组,不然会出现异常
this._args = args;
// 钩子的名字,目前没有发现哪里有用到
this.name = name;
// 存放tap函数的参数
this.taps = [];
// 拦截器数组
this.interceptors = [];
// 暴露出去用于调用同步钩子的函数
this._call = CALL_DELEGATE;
this.call = CALL_DELEGATE;
// 暴露出去的用于调用异步钩子函数
this._callAsync = CALL_ASYNC_DELEGATE;
this.callAsync = CALL_ASYNC_DELEGATE;
// 暴露出去的用于调用异步promise函数
this._promise = PROMISE_DELEGATE;
this.promise = PROMISE_DELEGATE;
// 用于生成调用函数的时候,保存钩子数组的变量
this._x = undefined;
// 继承子类的实现方法
this.compile = this.compile;
this.tap = this.tap;
this.tapAsync = this.tapAsync;
this.tapPromise = this.tapPromise;
}
// ... ...
}
② 问题二:调用tap方法后发生了什么事情呢?
tap(options, fn) {
this._tap("sync", options, fn);
}
_tap(type, options, fn) {
// 参数的限制与参数的转化
if (typeof options === "string") {
options = {
name: options
};
} else if (typeof options !== "object" || options === null) {
throw new Error("Invalid tap options");
}
if (typeof options.name !== "string" || options.name === "") {
throw new Error("Missing name for tap");
}
if (typeof options.context !== "undefined") {
deprecateContext();
}
options = Object.assign({ type, fn }, options);
// 注册拦截器
options = this._runRegisterInterceptors(options);
// 插入钩子
this._insert(options);
}
③ 问题三:接下来看看注册拦截器做了什么事情
_runRegisterInterceptors(options) {
// 到现在这个参数应该是 {name: "event01", type: "sync", fn: function ... }
for (const interceptor of this.interceptors) {
if (interceptor.register) {
// 把options传入拦截器注册,拦截器的register 可以返回一个新的options选项,
// 并且替换掉原来的options选项,也就是说可以在执行了一次register之后 改变你当初 tap 进去的方法
// 这种方式可以说很好理解了,就是在真正执行tap之前将options进行拦截处理,返回一个新的options
// 在使用上需要注意一些细节,intercept需要在tap之前执行
const newOptions = interceptor.register(options);
if (newOptions !== undefined) {
options = newOptions;
}
}
}
return options;
}
这种典型的拦截处理方式很好理解(常规操作),就是在真正执行tap之前将options进行拦截处理,返回一个新的options,在使用上intercept需要在tap之前执行。那么下一个问题,拦截器如何加入进去的呢?
④ 问题四:拦截器如何添加进去的?
intercept(interceptor) {
// 重置 子类重写的方法, 官方文档提到过 编译出来的调用方法对截器存在一定的依赖性. 所有每添加一个拦截器都要重置一次调用方法,在下一次编译的时候,重新生成.
this._resetCompilation();
// 保存原有引用的前提下复制一份拦截器对象
this.interceptors.push(Object.assign({}, interceptor));
if (interceptor.register) {
// 将taps对象作为拦截对象,写的很亲切了
for (let i = 0; i < this.taps.length; i++) {
this.taps[i] = interceptor.register(this.taps[i]);
}
}
}
⑤ 问题五:_insert函数做了什么事情呢?
_insert(item) {
// 重置子类重写方法
this._resetCompilation();
// 这个before就是案例taphook.tap方法的第一个参数,用在webpack当中就是钩子的名字
let before;
// 这个before可以是多个差价的名字
if (typeof item.before === "string") {
before = new Set([item.before]);
} else if (Array.isArray(item.before)) {
before = new Set(item.before);
}
let stage = 0;
if (typeof item.stage === "number") {
stage = item.stage;
}
let i = this.taps.length;
while (i > 0) {
i--;
const x = this.taps[i];
this.taps[i + 1] = x;
// 默认stage为0
const xStage = x.stage || 0;
if (before) {
if (before.has(x.name)) {
before.delete(x.name);
continue;
}
if (before.size > 0) {
continue;
}
}
if (xStage > stage) {
continue;
}
i++;
break;
}
this.taps[i] = item; // 把挪出来的位置插入传进来的钩子
}
一个根据before和stage来排序的简单排序算法, 可以看到stage的权重比before的权重略大
仔细想来tap其实就做了参数拦截处理和调整taps的顺序两件事情,接下来看下案例中
taphook.call做了什么事情
⑥ 问题六:案例中taphook.call发生了什么事情呢?
const CALL_DELEGATE = function(...args) {
// 这个整个tapable架构被我称之为艺术的核心所在了,通过new Function()生成fn并缓存,这样做能够减少内存的消耗
this.call = this._createCall("sync");
return this.call(...args);
};
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
compile(options) {
// 强制要求子类重写
throw new Error("Abstract: should be overridden");
}
⑦ 问题七:compile内部做了什么事情呢?
方法强制要求子类重写,来看看子类SyncHook的实现
const factory = new SyncHookCodeFactory();
const COMPILE = function(options) {
// options:
// {
// taps: this.taps, tap对象数组
// interceptors: this.interceptors, 拦截器数组
// args: this._args,
// type: type
// }
factory.setup(this, options);
return factory.create(options);
};
// HookCodeFactory.js
class HookCodeFactory {
constructor(config) {
// 其他类使用到
this.config = config;
this.options = undefined;
this._args = undefined;
}
setup(instance, options) {
// instance 为SyncHook的实例,将所有的注册钩子事件函数以数组的形式 缓存 至 _x 中
instance._x = options.taps.map(t => t.fn);
}
init(options) {
this.options = options;
this._args = options.args.slice();
}
// 贴个申明,详细代码看后续
header() { }
create(options) {
this.init(options);
let fn;
switch (this.options.type) {
case "sync":
// 动态生成一个钩子函数
fn = new Function(
this.args(), // 案例当中就是 “speed, speed2”
'"use strict";\n' +
this.header() +
this.contentWithInterceptors({
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})
);
break;
// 其他情况暂时不看 ...
}
// 把上面 init() 赋的值重置为 undefined
// this.options = undefined;
// this._args = undefined;
this.deinit();
return fn;
}
}
⑧ 问题八:问题七当中提到的this.header()做了什么事情呢?
class HookCodeFactory {
header() {
let code = "";
// 遍历判断判断this.options.taps[i] 是否 有context 属性, 任意一个tap有 都会返回 true
if (this.needContext()) {
// 如果有context 属性, 那_context这个变量就是一个空的对象.
code += "var _context = {};\n";
} else {
// 否则 就是undefined
code += "var _context;\n";
}
// 在setup()中 把所有tap对象的钩子 都给到了 instance ,这里的this 就是setup 中的instance _x 就是钩子对象数组
code += "var _x = this._x;\n";
if (this.options.interceptors.length > 0) {
// 如果有拦截器, 保存taps 数组到_taps变量, 保存拦截器数组 到变量_interceptors
code += "var _taps = this.taps;\n";
code += "var _interceptors = this.interceptors;\n";
}
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.call) {
// getInterceptor 返回的 是字符串 是 `_interceptors[i]`
// 一个拦截器只会生成一次call, 并且在call拦截器的时候传入("_context, speed, speed2")
code += `${this.getInterceptor(i)}.call(${this.args({
before: interceptor.context ? "_context" : undefined
})});\n`;
}
}
// 返回一个可以执行的javascript字符串
// 我们的例子中返回的是 "var _context;\nvar _x = this._x;\n"
return code;
}
getInterceptor(idx) {
return `_interceptors[${idx}]`;
}
}
⑨ 问题九:问题七中的this.contentWithInterceptors发生了什么事情呢?
class HookCodeFactory {
contentWithInterceptors(options) {
if (this.options.interceptors.length > 0) {
const onError = options.onError;
const onResult = options.onResult;
const onDone = options.onDone;
// 对参数做出相应处理
return this.content(
// ... ...
);
} else {
return this.content(options);
}
}
// ...
}
class SyncHookCodeFactory extends HookCodeFactory {
// 实际上调用了callTapsSeries 事件,见上面的代码
content({ onError, onDone, rethrowIfPossible }) {
// 改变了onError事件,暂时没有想明白为什么这样做
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
class HookCodeFactory {
callTapsSeries({
onError, // 案例中传参了
onResult, // 案例中为undefined
resultReturns, // 案例中为undefined
onDone, // 案例中传参了
doneReturns, // 案例中为undefined
rethrowIfPossible // 案例中传参了
}) {
// onDone返回一个空串“”
if (this.options.taps.length === 0) return onDone();
// 如果存在异步钩子,把=获取第一个异步钩子的下标,如果没有这个返回的是-1
const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");
// 我们的案例这里为false
const somethingReturns = resultReturns || doneReturns || false;
let code = "";
let current = onDone;
for (let j = this.options.taps.length - 1; j >= 0; j--) {
const i = j;
const unroll = current !== onDone && this.options.taps[i].type !== "sync";
if (unroll) {
code += `function _next${i}() {\n`;
code += current();
code += `}\n`;
current = () => `${somethingReturns ? "return " : ""}_next${i}();\n`;
}
const done = current;
// 字面意思,是否跳过done 是就增加一个跳出递归的条件
const doneBreak = skipDone => {
if (skipDone) return "";
return onDone();
};
// callTap的调用代码具体看下面的函数
const content = this.callTap(i, {
// 调用的onError 是 (i, err) => onError(err) , 后面这个onError(err)是 () => `throw ${err}`, 会将i丢弃
// 目前 i done doneBreak 三个参数都没有使用到
onError: error => onError(i, error, done, doneBreak),
// 这里onResult 同步钩子的情况下在外部是没有传进来的,刚才也提到了
// 这里onResult是 undefined
onResult:
onResult &&
(result => {
return onResult(i, result, done, doneBreak);
}),
// 没有onResult 一定要有一个onDone 所以这里就是一个默认的完成回调
// 这里的done 执行的是next(i+1), 也就是迭代的处理完所有的taps
onDone: !onResult && done,
// rethrowIfPossible 默认是 true 也就是返回后面的
// 因为没有异步函数 firstAsync = -1.
// 所以返回的是 -1 < 0,也就是true, 这个可以判断当前的是否是异步的tap对象
// 这里处理的妙啊 如果是 false 那么当前的钩子类型就不是sync,可能是promise或者是async
// 具体作用要看callTaps()如何使用这个.
rethrowIfPossible:
rethrowIfPossible && (firstAsync < 0 || i < firstAsync)
});
// 这里没看懂,惰性执行吗?
current = () => content;
}
code += current();
return code;
}
/**
* tapIndex 当前钩子所在的游标位置
* onError:() => onError(i,err,done,skipdone) ,
* onReslt: undefined
* onDone: () => {return: done()} //递归结束
* rethrowIfPossible: false 说明当前的钩子不是sync的.
*/
callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
let code = "";
// hasTapCached 是否有tap的缓存
let hasTapCached = false;
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.tap) {
if (!hasTapCached) {
// 这里生成的代码就是 `var _tap0 = _taps[0]`
// _taps 变量我们在 header 中生成
code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`;
// 如果这里执行多次,就会生成多个重复代码,不稳定,也影响性能.
hasTapCached = true;
}
// 我们的案例产生的其中一个code结果
// _interceptor[0].tap(_tap0);
code += `${this.getInterceptor(i)}.tap(${
interceptor.context ? "_context, " : ""
}_tap${tapIndex});\n`;
}
}
// 这里在案例中的code为 var _fn0 = _x[0]
code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
// 开始处理tap对象
const tap = this.options.taps[tapIndex];
switch (tap.type) {
case "sync":
// 在可能抛出错误的情况下,增加try {} catch(e) {}
if (!rethrowIfPossible) {
code += `var _hasError${tapIndex} = false;\n`;
code += "try {\n";
}
// 同步的时候 onResult 是 undefined
// 我们也分析一下如果走这里会怎样
// var _result0 = _fn0(option)
// 可以看到是调用tap 进来的钩子并且接收参数
// ps:看官网介绍时好几个返回undefined的钩子,看着就在猜这个问题的结果,源码看到这里我笑了,果然如果猜测一样,有就接受结果,哈哈~
if (onResult) {
code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
} else {
code += `_fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
}
// 把 catch 补上,在这个例子中没有
if (!rethrowIfPossible) {
code += "} catch(_err) {\n";
code += `_hasError${tapIndex} = true;\n`;
code += onError("_err");
code += "}\n";
code += `if(!_hasError${tapIndex}) {\n`;
}
// 有onResult 就把结果给传递出去.
if (onResult) {
code += onResult(`_result${tapIndex}`);
}
// 有onDone() 就调用他开始递归,上面callTapsSeries函数的next(i)
if (onDone) {
code += onDone();
}
if (!rethrowIfPossible) {
code += "}\n";
}
break;
// case "async" 和 case "promise"
// ... ...
}
// 我们的例子中,code为 "var _fn2 = _x[2];\n_fn2(speed, speed2);\n"
return code;
}
// ...
}
⑨ + ①:小结:将各个问题小结一下
来看看this._createCall具体执行结果是什么
// {
// taps: this.taps,
// interceptors: this.interceptors,
// args: this._args,
// type: type
// }
const CALL_DELEGATE = function(...args) {
// 现在回味这句话,你觉得能称之为艺术吗?
// 这个整个tapable架构被我称之为艺术的核心所在了,通过new Function()生成fn并缓存,这样做能够减少内存的消耗
// this.call 就是上面的匿名函数
this.call = this._createCall("sync");
return this.call(...args);
};
经过上述分析得出,我们案例中相关的参数在this.call执行时,具体如下
// ① _args
[ 'user01-speed-params', 'user02-speed2-params' ]
// ② this._x
[
(args1, args2) => {console.log("event03: ", args1, args2)}
(args1, args2) => {console.log("event02: ", args1, args2)}
(args1, args2) => {console.log("event01: ", args1, args2)}
]
// ③ this.call执行函数,this._createCall("sync")的返回值
function anonymous(speed, speed2
) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(speed, speed2);
var _fn1 = _x[1];
_fn1(speed, speed2);
var _fn2 = _x[2];
_fn2(speed, speed2);
}
最后看看匿名函数执行结果是多少
event03: user01-speed-params user02-speed2-params
event02: user01-speed-params user02-speed2-params
event01: user01-speed-params user02-speed2-params
经过九个问题加上一个小结,我们追踪跟进发现taphook实例、tap和call实际执行的函数和相关结果了,到这里也就差不多将整个taphook的核心基本了解明白了。
#3 小结一下
小结一下整个demo的执行流程
- ① 实例创建:创建SyncHook实例,通过继承的方式重写相关方法
- ② Tap方法:先校验 options 参数的格式,再走到 _runRegisterInterceptors 方法,这一步是为了执行拦截器的 register 方法,来生成新的 options。
- ② Tap方法:接着走到 _insert 内部,内部根据 before、stage 属性来调整 handler 的顺序(我的理解是stage的权重比before略大),并且将所有的信息保存到 taps 数组里面。
- ③ Call方法:执行 call,就是执行了代理函数CALL_DELEGATE,也就是执行了 _createCall, 通过call来执行_createCall返回惰性函数字符串
- ④ Call方法:_createCall 内部执行了 compile 方法,这个方法在 SyncHook 的原型上。compile 的内部先执行 SyncHookCodeFactory 上的 setup 方法,然后执行 create 方法。setup 与 create 方法都是在 HookCodeFactory 的原型上,因为 SyncHookCodeFactory 是继承于 HookCodeFactory。
- ⑤ Call方法:setup 内部的逻辑很简单,就是从 taps 数组的值传递给_x,供后续create生成的函数调用
- ⑥ Call方法:create 内部先初始化 options 参数,这个是在调用 compile 的时候传入的,然后通过字符串拼接执行 new Function 得到 fn,最后执行的也是这个 fn。
#4 调试部分
如果你还想看下其他的异步钩子如何执行,只需要写好demo通过对应的工具调试即可。贴出我在学习源码是写的一个异步hook的demo,方便调试使用
const { AsyncParallelHook,AsyncSeriesWaterfallHook,AsyncSeriesHook }= require('tapable');
class Person{
constructor(){
//所有的钩子都暴露在hooks属性上--官方推荐用法
this.hooks={
//速度
run:new AsyncParallelHook(['speed']),
runSeries:new AsyncSeriesHook(['speed']),
runPromise:new AsyncSeriesWaterfallHook(['speed'])
}
}
runAsync(speed){
this.hooks.run.callAsync(speed,()=>{
console.info('runAsync:Async Parallel call complete!!!');
});
}
runSeries(speed){
this.hooks.runSeries.callAsync(speed,()=>{
console.info('runSeries:Async Series call complete!!!');
});
}
runPromise(speed){
this.hooks.runPromise.promise(speed).then(ret=>{
console.info(`runPromise:Async Waterfall Promise call ret===${ret}`)
})
}
}
//记录跑的速度 Phone SportsBracelet ...
const student =new Person();
/**
* Parallel
*/
student.hooks.run.tapAsync('phone',(speed,done)=>{
setTimeout(()=>{
console.info(`Async Parallel phone 5s -->${speed}`);
done();
// do anthor something ..
},5000)
})
student.hooks.run.tapAsync('otherDevice',(speed,done)=>{
setTimeout(()=>{
console.info(`Async Parallel otherDevice 3s -->${speed}`);
done();
// do anthor something ..
},3000)
})
student.hooks.run.tapAsync('SportsBracelet',(speed,done)=>{
setTimeout(()=>{
console.info(`Async Parallel SportsBracelet 2s -->${speed}`);
done();
// do anthor something ..
},2000)
})
/**
* Series
*/
student.hooks.runSeries.tapAsync('phone',(speed,done)=>{
console.info(`Async Series 1, tapAsync`);
setTimeout(()=>{
console.info(`Async Series phone 5s -->${speed}`);
done();
// do anthor something ..
},5000)
})
student.hooks.runSeries.tapAsync('otherDevice',(speed,done)=>{
console.info(`Async Series 2, tapAsync`);
setTimeout(()=>{
console.info(`Async Series otherDevice 3s -->${speed}`);
done();
// do anthor something ..
},3000)
})
student.hooks.runSeries.tapAsync('SportsBracelet',(speed,done)=>{
console.info(`Async Series 3, tapAsync`);
setTimeout(()=>{
console.info(`Async Series SportsBracelet 2s -->${speed}`);
done();
// do anthor something ..
},2000)
})
/**
* Promise
*/
student.hooks.runPromise.tapPromise('promisePhone',(speed)=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
console.info(`Async SeriesWaterfall 2s -->${speed}`);
resolve(speed);
// do something ..
},2000)
})
})
student.runAsync('2km/h');
student.runSeries('2.3km/h');
student.runPromise(120);
#5 总结
- 我是第一次在源码当中看到这种字符串拼接的代码书写方式,给我带来了很大的启发,其中最大的收获:通过字符串的方式缓存每次编译的生成的 fn,能够减少内存占用情况,使得代码性能提升。
- 思考:拦截器的处理模块是否能够简化呢?洋葱模型可以简化它吗?又或者启发方式能够简化拦截器的处理?
#6 Flag
看完了tapable的事件流处理方式,突然想对比下node的events模块了,文章拖延了半个月,这次立下flag,下周(4.30)更新events源码
