Skip to content

SyncLoopHock

同步循环执行

特点

  1. 串行同步执行
  2. 事件处理函数返回 true 表示继续循环,如果返回 undefined 的话,表示结束循环。

例子

js
const { SyncLoopHook } = require("tapable")

// 创建一个同步事件流实例对象
const h1 = new SyncLoopHook(["name", "age"])

let timer = 0

h1.intercept({
  call: (source, target, routesList) => {
    console.log("Starting to SyncLoopHook")
  },
  register: tapInfo => {
    console.log(`${tapInfo.name} is doing its job`)
    return tapInfo
  },
  tap: tap => {
    console.log(tap.name)
  },
})

// 添加同步事件A
h1.tap("A", function (name, age) {
  console.log("A", name)
  console.log("A", age)
  timer++
  if (timer == 10) {
    return undefined
  } else {
    return true
  }
})

// 添加同步事件B
h1.tap("B", function (name, age) {
  console.log("B", name)
  console.log("B", age)
})

// 添加同步事件B
h1.tap("C", function (name, age) {
  console.log("C", name)
  console.log("C", age)
})

// 添加同步事件F
h1.tap(
  {
    name: "F",
    before: "D",
  },
  function (name, age) {
    console.log("F", name)
    console.log("F", age)
    console.log("F : timer", timer)
    timer++
    if (timer == 4) {
      return undefined
    } else {
      return true
    }
  }
)
// 添加同步事件E
h1.tap(
  {
    name: "E",
    before: "C",
  },
  function (name, age) {
    console.log("E", name)
    console.log("E", age)
  }
)
// 添加同步事件D
h1.tap("D", function (name, age) {
  console.log("D", name)
  console.log("D", age)
})

// 执行这个事件流
h1.call("gzh", 20)

看上面的例子我们想到的输出结果是不是

F , F , F , F , F , A , A , A , A , A , A , B , E , C , D

但是实际上其进入的是死循环

为什么? 我们看上面 tapable 可执行的代码字符串是什么。

我们找到 SyncLoopHook 的 content

js
class SyncLoopHookCodeFactory extends HookCodeFactory {
  content({ onError, onResult, onDone, rethrowIfPossible }) {
    return this.callTapsLooping({
      onError: (i, err) => onError(err),
      onDone,
      rethrowIfPossible,
    })
  }
}

发现其不是使用的 callTapsSeries 而是使用的 callTapsLooping,那我们先看一下 callTapsLooping

js
class HookCodeFactory {
  callTapsLooping({ onError, onDone, rethrowIfPossible }) {
    if (this.options.taps.length === 0) return onDone()
    const syncOnly = this.options.taps.every(t => t.type === "sync")
    let code = ""
    if (!syncOnly) {
      code += "var _looper = () => {\n"
      code += "var _loopAsync = false;\n"
    }
    code += "var _loop;\n"
    code += "do {\n"
    code += "_loop = false;\n"

    // 处理每一个拦截器的 loop 方法
    for (let i = 0; i < this.options.interceptors.length; i++) {
      const interceptor = this.options.interceptors[i]
      if (interceptor.loop) {
        code += `${this.getInterceptor(i)}.loop(${this.args({
          before: interceptor.context ? "_context" : undefined,
        })});\n`
      }
    }
    code += this.callTapsSeries({
      onError,
      onResult: (i, result, next, doneBreak) => {
        let code = ""
        code += `if(${result} !== undefined) {\n`
        code += "_loop = true;\n"
        if (!syncOnly) code += "if(_loopAsync) _looper();\n"
        code += doneBreak(true)
        code += `} else {\n`
        code += next()
        code += `}\n`
        return code
      },
      onDone:
        onDone &&
        (() => {
          let code = ""
          code += "if(!_loop) {\n"
          code += onDone()
          code += "}\n"
          return code
        }),
      rethrowIfPossible: rethrowIfPossible && syncOnly,
    })
    code += "} while(_loop);\n"
    if (!syncOnly) {
      code += "_loopAsync = true;\n"
      code += "};\n"
      code += "_looper();\n"
    }
    return code
  }
}

发现其也是通过 this.callTapsSeries() 只是其不可以简简单单的在每一个事件流的后面通过 onResult 去包装一层,其需要在整体外面包装一层 do { } while (_loop);,然后在每一个事件流最后添加一个 if (_result0 !== undefined) {_loop = true;} else {} 这样只要 返回值不是 undefined 那么就不会执行下面的代码(事件流),且 _loop = true;触发do while

js
do {
  _loop = false
  if (_result0 !== undefined) {
    _loop = true
  } else {
    // 下一个

    if (_result0 !== undefined) {
      _loop = true
    } else {
    }
  }
} while (_loop)

我们看一下一个生成的代码

js
;(function anonymous(name, age) {
  "use strict"
  var _context
  var _x = this._x
  var _taps = this.taps
  var _interceptors = this.interceptors
  _interceptors[0].call(name, age)
  var _loop
  do {
    _loop = false
    var _tap0 = _taps[0]
    _interceptors[0].tap(_tap0)
    var _fn0 = _x[0]
    var _result0 = _fn0(name, age)
    if (_result0 !== undefined) {
      _loop = true
    } else {
      var _tap1 = _taps[1]
      _interceptors[0].tap(_tap1)
      var _fn1 = _x[1]
      var _result1 = _fn1(name, age)
      if (_result1 !== undefined) {
        _loop = true
      } else {
        var _tap2 = _taps[2]
        _interceptors[0].tap(_tap2)
        var _fn2 = _x[2]
        var _result2 = _fn2(name, age)
        if (_result2 !== undefined) {
          _loop = true
        } else {
          var _tap3 = _taps[3]
          _interceptors[0].tap(_tap3)
          var _fn3 = _x[3]
          var _result3 = _fn3(name, age)
          if (_result3 !== undefined) {
            _loop = true
          } else {
            var _tap4 = _taps[4]
            _interceptors[0].tap(_tap4)
            var _fn4 = _x[4]
            var _result4 = _fn4(name, age)
            if (_result4 !== undefined) {
              _loop = true
            } else {
              var _tap5 = _taps[5]
              _interceptors[0].tap(_tap5)
              var _fn5 = _x[5]
              var _result5 = _fn5(name, age)
              if (_result5 !== undefined) {
                _loop = true
              } else {
                if (!_loop) {
                }
              }
            }
          }
        }
      }
    }
  } while (_loop)
})

可见前面的我们 输出 5 次 F 是对的,因为这时候 timer < 4 所以其一直执行第一个循环,然后等于 4 的时候,F 返回 undefined 了,进入下一个事件流了(A),但是我们看上面的代码发现其不是下一个循环只是执行 A 而是从新从头执行一次,但是这时候 timer === 5 了那么 F 中 timer 永远不会等于 4 了,然后就无限执行 F.

总结

  1. 想要进入下一个事件流其返回的不是 false 而是 undefined

  2. 其在中间事件流触发循环不是只是循环执行当前事件流,而是从头开始执行整个事件流

上次更新: