tapable 是一个类似于 Node.js 中的 EventEmitter的库,但更专注于自定义事件的触发和处理。
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook,
AsyncSeriesLoopHook
} = require("tapable");
这些钩子有同步钩子和异步钩子之分。
Sync-开头的是同步钩子,
Async-开头的是异步钩子。
异步钩子又分为异步串行钩子(AsyncSeries)和异步并行钩子(AsyncParallel)。
*Hook:基础钩子,单纯的调用注册的事件回调,并不关心其内部的运行逻辑
*BailHook:熔断钩子,当一个事件回调运行时返回的值不为undefined时,停止后面事件回调的执行
*WaterfallHook: 瀑布钩子,如果当前执行的事件回调返回值不为undefined时,那么就把下一个事件回调的第一个参数替换成这个值
*LoopHook:循环钩子,如果当前执行的事件回调的返回值不是undefined,重新从第一个注册的事件回调处执行,直到当前执行的事件回调没有返回值
npm install --save tapable
const {
SyncHook,
} = require("tapable");
// 实例化hook
const hook = new SyncHook(["arg1", "arg2", "arg3"]);
// 注册回调事件
hook.tap("vue", function (name) {
console.log("vue", name);
return "vue-book";
});
hook.tap("react", function (name) {
console.log("react:", name);
return "react";
});
// 触发回调
hook.call('ac')
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的一种形式,可以把Hook看成抽象类,看一下基础Hook的实现:
./lib/Hook.js
...
class Hook {
constructor(args = [], name = undefined) {
this._args = args;
this.name = name;
this.taps = [];
this.interceptors = [];
this._call = CALL_DELEGATE;
this.call = CALL_DELEGATE;
this._callAsync = CALL_ASYNC_DELEGATE;
this.callAsync = CALL_ASYNC_DELEGATE;
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;
}
_createCall(type) {
...
}
_tap(type, options, fn) {
...
}
_insert(item) {
...
}
...
}
Object.setPrototypeOf(Hook.prototype, null);
module.exports = Hook;
实例化之后hook实例具有如下属性和方法:
2. 注册回调函数
hook.tap("vue", function (name) {
console.log("vue", name);
return "vue-book";
});
hook.tap("react", function (name) {
console.log("react:", name);
return "react";
});
执行这一步的,会直接执行hook的tap方法,hook的tap方法我们看到在Hook类实现的
./lib/Hook.js
_tap(type, options, fn) {
if (typeof options === "string") {
options = {
name: options.trim()
};
} else if (typeof options !== "object" || options === null) {
throw new Error("Invalid tap options");
}
...
this._insert(options);
}
tap(options, fn) {
this._tap("sync", options, fn);
}
tapAsync(options, fn) {
this._tap("async", options, fn);
}
tapPromise(options, fn) {
this._tap("promise", options, fn);
}
tap方法会执行_insert方法
_insert(item) {
...
this.taps[i] = item;
}
至此,回调函数注册完毕,结果如下:
3. 调用回调函数 hook.call()
执行hook.call()会执行下面代码(./lib/Hook.js):
const CALL_DELEGATE = function(...args) {
this.call = this._createCall("sync");
return this.call(...args);
};
接着执行_createCall方法(./lib/Hook.js)
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
然后会执行compile方法,compile方法在SyncHook里面定义的(./lib/SyncHook.js)
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
这里的factory指的是什么呢?接着向下看:
class SyncHookCodeFactory extends HookCodeFactory {
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
const factory = new SyncHookCodeFactory();
factory是SyncHookCodeFactory的实例,SyncHookCodeFactory这类继承了HookCodeFactory,HookCodeFactory这个类就实现了根据不同的参数生成不同的函数体,所以我们执行compile中的factory.setup()方法走到了HookCodeFactory中的setup(./lib/HookCodeFactory.js)
setup(instance, options) {
instance._x = options.taps.map(t => t.fn);
}
这个方法就是把我们注册的回调函数赋给了this._x,此时的hook实例如下:
接着执行factory.create(),这个方法通过new Function()根据不同的参数生成不同的函数体,
create(options) {
this.init(options);
let fn;
switch (this.options.type) {
case "sync":
fn = new Function(
this.args(),
'"use strict";\n' +
this.header() +
this.contentWithInterceptors({
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})
);
break;
case "async":
fn = new Function(
this.args({
after: "_callback"
}),
'"use strict";\n' +
this.header() +
this.contentWithInterceptors({
onError: err => `_callback(${err});\n`,
onResult: result => `_callback(null, ${result});\n`,
onDone: () => "_callback();\n"
})
);
break;
case "promise":
let errorHelperUsed = false;
const content = this.contentWithInterceptors({
onError: err => {
errorHelperUsed = true;
return `_error(${err});\n`;
},
onResult: result => `_resolve(${result});\n`,
onDone: () => "_resolve();\n"
});
let code = "";
code += '"use strict";\n';
code += this.header();
code += "return new Promise((function(_resolve, _reject) {\n";
if (errorHelperUsed) {
code += "var _sync = true;\n";
code += "function _error(_err) {\n";
code += "if(_sync)\n";
code +=
"_resolve(Promise.resolve().then((function() { throw _err; })));\n";
code += "else\n";
code += "_reject(_err);\n";
code += "};\n";
}
code += content;
if (errorHelperUsed) {
code += "_sync = false;\n";
}
code += "}));\n";
fn = new Function(this.args(), code);
break;
}
this.deinit();
console.log(fn.toString())
return fn;
}
最终create方法返回的函数是:
function anonymous(arg1, arg2, arg3) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(arg1, arg2, arg3);
var _fn1 = _x[1];
_fn1(arg1, arg2, arg3);
}
这个也就是this._createCall()的结果,然后执行this.call()调用我们这个函数。
const CALL_DELEGATE = function(...args) {
this.call = this._createCall("sync");
return this.call(...args);
};
最终得出结果:
vue ac
react: ac
至此源码解析完毕,下面我们看一下同步和异步怎么实现的