Skip to content

SyncHook

串行同步执行执行

特点

  1. 串行同步执行
  2. 按照事件注册的先后顺序执行所有的事件处理函数

例子

  1. 最简单的
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

上次更新: