Appearance
SyncHook
串行同步执行执行
特点
- 串行同步执行
- 按照事件注册的先后顺序执行所有的事件处理函数
例子
- 最简单的
js
const { SyncHook } = require("tapable")
// 创建一个同步事件流实例对象
const h1 = new SyncHook(["xxx", "arg2"])
h1.intercept({
call: (source, target, routesList) => {
console.log("Starting to h1 routes")
},
register: tapInfo => {
// tapInfo = { type: "promise", name: "GoogleMapsPlugin", fn: ... }
console.log(`${tapInfo.name} is doing its job`)
return tapInfo // may return a new tapInfo object
},
tap: tap => {
console.log(tap, "444444")
},
})
// 添加同步事件A
h1.tap("A", function (args) {
console.log("A", args)
return "b"
})
// 添加同步事件B
h1.tap("B", function () {
console.log("b")
})
// 添加同步事件C
h1.tap("C", function () {
console.log("c")
})
// 添加同步事件F
h1.tap(
{
name: "F",
before: "D",
},
function () {}
)
// 添加同步事件E
h1.tap(
{
name: "E",
before: "C",
},
function () {}
)
// 添加同步事件D
h1.tap("D", function () {
console.log("d")
})
// 执行这个事件流
h1.call(7777)
可执行代码
js
"use strict"
var _context
var _x = this._x
var _taps = this.taps
var _interceptors = this.interceptors
_interceptors[0].call(xxx, arg2)
var _tap0 = _taps[0]
_interceptors[0].tap(_tap0)
var _fn0 = _x[0]
_fn0(xxx, arg2)
var _tap1 = _taps[1]
_interceptors[0].tap(_tap1)
var _fn1 = _x[1]
_fn1(xxx, arg2)
var _tap2 = _taps[2]
_interceptors[0].tap(_tap2)
var _fn2 = _x[2]
_fn2(xxx, arg2)
var _tap3 = _taps[3]
_interceptors[0].tap(_tap3)
var _fn3 = _x[3]
_fn3(xxx, arg2)
var _tap4 = _taps[4]
_interceptors[0].tap(_tap4)
var _fn4 = _x[4]
_fn4(xxx, arg2)
var _tap5 = _taps[5]
_interceptors[0].tap(_tap5)
var _fn5 = _x[5]
_fn5(xxx, arg2)
结果
js
A is doing its job
B is doing its job
C is doing its job
F is doing its job
E is doing its job
D is doing its job
Starting to h1 routes
F
F gzh
F 20
A
A name-F
A 20
B
B name-a
B 20
E
E name-b
E 20
C
C name-E
C 20
D
D name-C
D 20
源码详解
js
const Hook = require("./Hook")
const HookCodeFactory = require("./HookCodeFactory")
class SyncHookCodeFactory extends HookCodeFactory {
content({ onError, onResult, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible,
})
}
}
const factory = new SyncHookCodeFactory()
class SyncHook extends Hook {
tapAsync() {
throw new Error("tapAsync is not supported on a SyncHook")
}
tapPromise() {
throw new Error("tapPromise is not supported on a SyncHook")
}
compile(options) {
factory.setup(this, options)
return factory.create(options)
}
}
module.exports = SyncHook
前面我们通过事件执行顺序这篇知道事件执行的顺序,然后通过事件执行过程简要知道 tapable 是通过将事件转换成可以执行的代码字符串去处理的,那么对于 SyncHook 其是怎么转换的,具体可以看 factory.create(options)在 SyncHook 中的处理过程
对于 SyncHook 其 option.type === 'sync'
js
class HookCodeFactory {
constructor(config) {
this.config = config
this.options = undefined
}
create(options) {
this.init(options)
switch (this.options.type) {
// Sync执行 sync的过程
case "sync":
let code1 = ""
code1 =
'"use strict";\n debugger; \n' +
this.header() +
this.content({
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
onDone: () => "",
rethrowIfPossible: true,
})
console.log(code1)
return new Function(this.args(), code1)
case "async":
case "promise":
}
}
}
module.exports = HookCodeFactory
从上面可以发现其主要分为两个部分:一个 this.header(); 一个 this.content({onError,onResult,onDone,rethrowIfPossible})
this.content()
生成可以执行的代码字符串的主体内容部分,其每一个钩子函数是不同的,但是也存在共同的部分
js
class SyncHookCodeFactory extends HookCodeFactory {
content({ onError, onResult, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible,
})
}
}
我们看 SyncHook 中 SyncHookCodeFactory 重写了 content 方法,其调用 this.callTapsSeries()
js
class HookCodeFactory {
/**
* **核心**
* 生成每一个事件流执行的代码字符串
*
*/
callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
let code = ""
let hasTapCached = false
// 处理拦截器中 的 interceptor.tap
/*
var _context;
var _x = this._x;
var _taps = this.taps;
var _interceptors = this.interceptors;
_interceptors[0].call(xxx, arg2);
var _tap0 = _taps[0];
_interceptors[0].tap(_tap0);
var _fn0 = _x[0];
_fn0(xxx, arg2);
var _tap1 = _taps[1];
_interceptors[0].tap(_tap1);
var _fn1 = _x[1];
_fn1(xxx, arg2);
我们看上面生成的结果可以看出 tap 在生成可执行的字符串的时候是 在每一个事件流回调执行之前执行执行当前tap拦截,参数为每一个的事件流的 tap对象 其实就是 { name , fn ...}
var _tap0 = _taps[0];
_interceptors[0].tap(_tap0);
*/
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i]
if (interceptor.tap) {
// 原来所有的 taps是存放在 this._taps 中,下面需要多次访问那么为了提高性能就缓存一份 var _tap0 = _taps[0];
// 在上面的例子中可能不能看出问题,主要是当我们注册很多拦截器的时候
// 这时候就是
/*
var _tap0 = _taps[0];
_interceptors[0].tap(_tap0);
_interceptors[1].tap(_tap0);
_interceptors[2].tap(_tap0);
_interceptors[3].tap(_tap0);
var _fn0 = _x[0];
_fn0(xxx, arg2);
*/
if (!hasTapCached) {
code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`
hasTapCached = true
}
code += `${this.getInterceptor(i)}.tap(${interceptor.context ? "_context, " : ""}_tap${tapIndex});\n`
}
}
// 生成 var _fn0 = _x[0];
code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`
const tap = this.options.taps[tapIndex]
// 下面生成 事件流的回调函数执行字符串
// 最简单的是 _fn0(xxx, arg2);
switch (tap.type) {
case "sync":
if (!rethrowIfPossible) {
code += `var _hasError${tapIndex} = false;\n`
code += "try {\n"
}
// 对于那些依赖上一个事件流的返回值的钩子函数,其不会只是简单的 _fn0(xxx, arg2);而是 var _result0 = _fn0(xxx, arg2);
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`
}
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}`)
}
if (onDone) {
code += onDone()
}
if (!rethrowIfPossible) {
code += "}\n"
}
break
case "async":
case "promise":
}
return code
}
/**
* 生成串行的事件流执行代码
* syncHook 也是串行处理的 其没有重写 onResult,所以在每一个事件流执行后都是直接执行 done()(next(i+1))
* SyncBailHook 也是串行处理的 但是其与syncHook的区别是 如果事件处理函数执行时有一个返回值不为空(即返回值为 undefined),则跳过剩下未执行的事件处理函数
*
*
*/
callTapsSeries({ onError, onResult, onDone, rethrowIfPossible }) {
if (this.options.taps.length === 0) return onDone()
const firstAsync = this.options.taps.findIndex(t => t.type !== "sync")
const next = i => {
// 执行的下标过大处理
if (i >= this.options.taps.length) {
return onDone()
}
// 生成下一个事件流执行的函数
const done = () => next(i + 1)
const doneBreak = skipDone => {
if (skipDone) return ""
return onDone()
}
// 核心还是通过 this.callTap()去生成
return this.callTap(i, {
onError: error => onError(i, error, done, doneBreak),
onResult:
onResult &&
(result => {
return onResult(i, result, done, doneBreak)
}),
onDone:
!onResult &&
(() => {
return done()
}),
rethrowIfPossible: rethrowIfPossible && (firstAsync < 0 || i < firstAsync),
})
}
return next(0)
}
}
module.exports = HookCodeFactory