美文网首页webpack
webpack学习(四)源码之tapable

webpack学习(四)源码之tapable

作者: 起飞之路 | 来源:发表于2019-09-26 18:58 被阅读0次

    tapable

    tapable 是 webpack 源码中到处可以看到的的一个事件处理机制,根本的设计思想就是发布订阅模式。看看 github 上的描述Just a little module for plugins.,主要是用来实现插件机制的。我们想要在插件在那个时期执行,怎样执行都依赖于这个模块

    主要用法

    对于 tapable 的用法网上有很多资料去介绍我这里就不去说了附上一个连接tapable 用法,主要就是将事件触发分为大概 3 种机制,同步执行,异步并发执行和异步排队阻塞执行

    核心模块

    我们从入口文件开始看起,先看一下目录结构

    tapable.png
    入口文件是 index.js 这个文件里只是将我们需要的模块做了一个整合并导出
    我们先从一个简单的同步顺序执行的 hook 开始看起我在一些主要方法上都加了注释,导出的是一个基于 Hook 生成对象,并重写了一些对象上的属性。
    • tapAsync,tapPromise 是异步触发的方法所以这里重写一下防止错误的调用
    • 还有一个主要的 compile 方法是基于HookCodeFactory这个工厂函数生成的,这是这个库中最核心的代码
    • 针对每一个 hook 都会继承这个工厂方法并重写 content 方法,后边会看到这里其实就是使用不同的方式去触发事件
    • 在最后我放了一个方法是生成的 call 函数,用来触发事件,后边会解释
    // SyncHook.js
    "use strict";
    
    const Hook = require("./Hook");
    const HookCodeFactory = require("./HookCodeFactory");
    
    class SyncHookCodeFactory extends HookCodeFactory {
      content({ onError, onDone, rethrowIfPossible }) {
        // 具体执行事件触发的主要方法
        return this.callTapsSeries({
          // 执行factory中的排队执行注册函数
          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");
    };
    
    const COMPILE = function(options) {
      factory.setup(this, options);
      return factory.create(options);
    };
    
    // 同步hook
    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;
    
    module.exports = SyncHook;
    
    /**
     * 生成call方法的工厂函数
     * 这是打印出来的一个例子
     */
    function anonymous(arg1, arg2) {
      "use strict";
      var _context;
      var _x = this._x;
      var _fn0 = _x[0];
      _fn0(arg1, arg2);
      var _fn1 = _x[1];
      _fn1(arg1, arg2);
    }
    

    我们来看一下真正的每个 hook 的类 Hook,我们会用到 tap 方法来注册事件,然后使用 call 方法来触发事件,针对所有的 hook 所有的 tap 方法除了 type 不一样基本上流程都差不多,看明白了这个基本上都明白了,我对一些关键字段都加了说明。

    • tap 触发的函数,都会保存到 taps 的数组中以供触发时使用,taps 中保存了触发函数,还保存了事件名称和事件类型,_x 中只保存了注册的回调函数我们在上班看到触发时也是直接使用_x 这个数组。
    • 我们主要来看一下 compile 这个方法,这个方法在每个 hook 中都会被重写,他最终会调用每个 hook 的工厂函数来生成一个方法,我们下边就来看看这个工厂方法
    // Hook.js
    "use strict";
    
    const util = require("util");
    
    const deprecateContext = util.deprecate(() => {},
    "Hook.context is deprecated and will be removed");
    
    // call函数的生成方法
    const CALL_DELEGATE = function(...args) {
      this.call = this._createCall("sync");
      console.log(this.call.toString());
      return this.call(...args);
    };
    const CALL_ASYNC_DELEGATE = function(...args) {
      this.callAsync = this._createCall("async");
      console.log(this.callAsync.toString());
      return this.callAsync(...args);
    };
    const PROMISE_DELEGATE = function(...args) {
      this.promise = this._createCall("promise");
      return this.promise(...args);
    };
    
    class Hook {
      constructor(args = [], name = undefined) {
        this._args = args; // 初始化传入的数组参数
        this.name = name; // 定义hook的名称可以不传
        this.taps = []; // 保存注册事件的所有参数的数组包括type,options,fn
        this.interceptors = []; // 拦截器
        this._call = CALL_DELEGATE; // 私有的call方法
        this.call = CALL_DELEGATE; // 暴露的call方法
        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;
      }
    
      // 编译生成call方法的接口,不继承会报错,js模拟面向接口编程
      compile(options) {
        throw new Error("Abstract: should be overridden");
      }
    
      // 生成call方法的中间方法
      _createCall(type) {
        return this.compile({
          taps: this.taps,
          interceptors: this.interceptors,
          args: this._args,
          type: type
        });
      }
    
      // 真正触发的注册事件方法
      _tap(type, options, fn) {
        // 检查传入的参数如果是字符串转为对象
        if (typeof options === "string") {
          options = {
            name: options
          };
        } // 检查options的类型必须为对象,并且必须有一个name字段
        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();
        }
        // 将需要的参数事件type,和回调函数合并到options中
        options = Object.assign({ type, fn }, options);
        // 运行拦截器的注册方法对options进行过滤
        options = this._runRegisterInterceptors(options);
        // 将注册的options加入tabs数组中以待之后使用
        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);
      }
    
      // 运行拦截器对options进行过滤
      _runRegisterInterceptors(options) {
        for (const interceptor of this.interceptors) {
          if (interceptor.register) {
            const newOptions = interceptor.register(options);
            if (newOptions !== undefined) {
              options = newOptions;
            }
          }
        }
        return options;
      }
    
      withOptions(options) {
        const mergeOptions = opt =>
          Object.assign({}, options, typeof opt === "string" ? { name: opt } : opt);
    
        return {
          name: this.name,
          tap: (opt, fn) => this.tap(mergeOptions(opt), fn),
          tapAsync: (opt, fn) => this.tapAsync(mergeOptions(opt), fn),
          tapPromise: (opt, fn) => this.tapPromise(mergeOptions(opt), fn),
          intercept: interceptor => this.intercept(interceptor),
          isUsed: () => this.isUsed(),
          withOptions: opt => this.withOptions(mergeOptions(opt))
        };
      }
      // 判断当前hooks是否被使用
      isUsed() {
        return this.taps.length > 0 || this.interceptors.length > 0;
      }
    
      intercept(interceptor) {
        this._resetCompilation();
        this.interceptors.push(Object.assign({}, interceptor));
        if (interceptor.register) {
          for (let i = 0; i < this.taps.length; i++) {
            this.taps[i] = interceptor.register(this.taps[i]);
          }
        }
      }
    
      // 重置用来触发hook的函数
      _resetCompilation() {
        this.call = this._call;
        this.callAsync = this._callAsync;
        this.promise = this._promise;
      }
    
      // 将注册的事件加入tabs数组中,再插入前,由于经过拦截器的过滤所以重置触发方法
      _insert(item) {
        this._resetCompilation();
        let 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;
          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;
      }
    }
    
    Object.setPrototypeOf(Hook.prototype, null);
    
    module.exports = Hook;
    

    这里代码比较多,所有触发事件的方法都在这里,我们可以先不看别的,直接看 create 方法,这里生成函数不是直接我们写代码时,直接根据参数去执行业务逻辑,使用了 es6 的Function构造函数来生成函数,这个方法接受 2 个参数,一个是函数的参数,一个是函数体,这样我们直接可以使用字符串来生成函数。

    • 我们顺着代码去看可以先看 args()这个方法,这是根据我们传入的参数,将参数转成字符串的方法
    • 然后是 header(),这个方法主要就是根据参数来初始化不同我们需要用到的变量
    • 最后是主要的函数执行逻辑也是之前看到的在 SyncHook 中重写的 content 的方法,具体逻辑我们还要跳回上边看一下是怎样执行的,调用了 callTapsSeries 这个方法表示排队执行每个事件
    • 我们来看一下这个方法也在 factory 中,我在代码中注释了主要的逻辑,这个方法就会生成一个函数我在 SyncHook 中添加的 anonymous 方法就是我写的一个例子在浏览器中打印出来的,可以看到他会同步在数组中取出回调并执行
    // HookCodeFactory.js
    "use strict";
    /**
     * Hook的生成工厂
     */
    class HookCodeFactory {
      constructor(config) {
        this.config = config;
        this.options = undefined;
        this._args = undefined;
      }
    
      /**
       * 根据传入的参数生成call函数的方法
       * @param {taps,interceptors,args,type} options
       */
      create(options) {
        // 将hook的参数传给工厂函数
        this.init(options);
        let fn;
        switch (this.options.type) {
          case "sync":
            fn = new Function(
              this.args(),
              '"use strict";\n' +
                this.header() +
                this.content({
                  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.content({
                  onError: err => `_callback(${err});\n`,
                  onResult: result => `_callback(null, ${result});\n`,
                  onDone: () => "_callback();\n"
                })
            );
            break;
          case "promise":
            let errorHelperUsed = false;
            const content = this.content({
              onError: err => {
                errorHelperUsed = true;
                return `_error(${err});\n`;
              },
              onResult: result => `_resolve(${result});\n`,
              onDone: () => "_resolve();\n"
            });
            let code = "";
            code += '"use strict";\n';
            code += "return new Promise((_resolve, _reject) => {\n";
            if (errorHelperUsed) {
              code += "var _sync = true;\n";
              code += "function _error(_err) {\n";
              code += "if(_sync)\n";
              code += "_resolve(Promise.resolve().then(() => { throw _err; }));\n";
              code += "else\n";
              code += "_reject(_err);\n";
              code += "};\n";
            }
            code += this.header();
            code += content;
            if (errorHelperUsed) {
              code += "_sync = false;\n";
            }
            code += "});\n";
            fn = new Function(this.args(), code);
            break;
        }
        this.deinit();
        return fn;
      }
    
      setup(instance, options) {
        instance._x = options.taps.map(t => t.fn);
      }
    
      /**
       * @param {{ type: "sync" | "promise" | "async", taps: Array<Tap>, interceptors: Array<Interceptor> }} options
       */
      init(options) {
        this.options = options;
        this._args = options.args.slice();
      }
    
      deinit() {
        this.options = undefined;
        this._args = undefined;
      }
    
      // 对需要变量进行初始化
      header() {
        let code = "";
        // 判断是否初始化_context
        if (this.needContext()) {
          code += "var _context = {};\n";
        } else {
          code += "var _context;\n";
        }
        // 将注册的回调函数给_x
        code += "var _x = this._x;\n";
        // 如果有拦截器初始化对应参数
        if (this.options.interceptors.length > 0) {
          code += "var _taps = this.taps;\n";
          code += "var _interceptors = this.interceptors;\n";
        }
        // 如果有拦截器调用拦截器的call方法传入所有参数,如果有context在参数最前边插入_context
        for (let i = 0; i < this.options.interceptors.length; i++) {
          const interceptor = this.options.interceptors[i];
          if (interceptor.call) {
            code += `${this.getInterceptor(i)}.call(${this.args({
              before: interceptor.context ? "_context" : undefined
            })});\n`;
          }
        }
        return code;
      }
    
      // 判断注册的事件中是否有context参数
      needContext() {
        for (const tap of this.options.taps) if (tap.context) return true;
        return false;
      }
    
      // 具体生成每一个注册函数的字符串
      callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
        let code = "";
        let hasTapCached = false;
        for (let i = 0; i < this.options.interceptors.length; i++) {
          const interceptor = this.options.interceptors[i];
          if (interceptor.tap) {
            if (!hasTapCached) {
              code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`;
              hasTapCached = true;
            }
            code += `${this.getInterceptor(i)}.tap(${
              interceptor.context ? "_context, " : ""
            }_tap${tapIndex});\n`;
          }
        }
        // 初始化回调函数变量形如_fn0,_fn1
        code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
        // 取得注册事件的所有参数
        const tap = this.options.taps[tapIndex];
        switch (tap.type) {
          case "sync":
            if (!rethrowIfPossible) {
              code += `var _hasError${tapIndex} = false;\n`;
              code += "try {\n";
            }
            if (onResult) {
              // SyncBailHook将函数执行结果给_result[i]变量
              code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
                before: tap.context ? "_context" : undefined
              })});\n`;
            } else {
              // SyncHook走这里单纯的执行函数
              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`;
            }
            if (onResult) {
              // 如果需要返回值则生成返回值代码
              code += onResult(`_result${tapIndex}`);
            }
            if (onDone) {
              // 新生成的字符串和之前生成的字符串
              code += onDone();
            }
            if (!rethrowIfPossible) {
              code += "}\n";
            }
            break;
          case "async":
            let cbCode = "";
            if (onResult) cbCode += `(_err${tapIndex}, _result${tapIndex}) => {\n`;
            else cbCode += `_err${tapIndex} => {\n`;
            cbCode += `if(_err${tapIndex}) {\n`;
            cbCode += onError(`_err${tapIndex}`);
            cbCode += "} else {\n";
            if (onResult) {
              cbCode += onResult(`_result${tapIndex}`);
            }
            if (onDone) {
              cbCode += onDone();
            }
            cbCode += "}\n";
            cbCode += "}";
            code += `_fn${tapIndex}(${this.args({
              before: tap.context ? "_context" : undefined,
              after: cbCode
            })});\n`;
            break;
          case "promise":
            code += `var _hasResult${tapIndex} = false;\n`;
            code += `var _promise${tapIndex} = _fn${tapIndex}(${this.args({
              before: tap.context ? "_context" : undefined
            })});\n`;
            code += `if (!_promise${tapIndex} || !_promise${tapIndex}.then)\n`;
            code += `  throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise${tapIndex} + ')');\n`;
            code += `_promise${tapIndex}.then(_result${tapIndex} => {\n`;
            code += `_hasResult${tapIndex} = true;\n`;
            if (onResult) {
              code += onResult(`_result${tapIndex}`);
            }
            if (onDone) {
              code += onDone();
            }
            code += `}, _err${tapIndex} => {\n`;
            code += `if(_hasResult${tapIndex}) throw _err${tapIndex};\n`;
            code += onError(`_err${tapIndex}`);
            code += "});\n";
            break;
        }
        return code;
      }
    
      /**
       * 正常排队执行生成的方法
       * @param {*} param0
       */
      callTapsSeries({
        onError, // 出错时调用方法
        onResult, // 返回值的方法
        resultReturns, // 是否需要返回值
        onDone, // 遍历拼接代码的初始代码
        doneReturns,
        rethrowIfPossible
      }) {
        // 如果注册事件为空直接返回执行onDone方法
        if (this.options.taps.length === 0) return onDone();
        // 找出事件数组中第一个异步注册事件
        const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");
        // return的结果
        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赋值为上一次遍历的结果以便在callTap中连接函数字符串
          const doneBreak = skipDone => {
            if (skipDone) return "";
            return onDone();
          };
          // 执行具体每一个回调生成字符串
          const content = this.callTap(i, {
            onError: error => onError(i, error, done, doneBreak),
            onResult:
              onResult &&
              (result => {
                return onResult(i, result, done, doneBreak);
              }),
            onDone: !onResult && done,
            rethrowIfPossible:
              rethrowIfPossible && (firstAsync < 0 || i < firstAsync)
          });
          /*将content赋值为当前生成的函数字符串以待下次循环使用,
                    Sync里的基本情况下直接连接,Bail的情况下传入到else的方法体里,Waterfail直接连接在代码后,第一次会在最后return结果
                */
          current = () => content;
        }
        // 将遍历后的结果于之前初始化的参数连接起来
        code += current();
        return code;
      }
    
      /**
       * 循环遍历执行
       * @param {*} param0
       */
      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";
        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;
      }
    
      callTapsParallel({
        onError,
        onResult,
        onDone,
        rethrowIfPossible,
        onTap = (i, run) => run()
      }) {
        if (this.options.taps.length <= 1) {
          return this.callTapsSeries({
            onError,
            onResult,
            onDone,
            rethrowIfPossible
          });
        }
        let code = "";
        code += "do {\n";
        code += `var _counter = ${this.options.taps.length};\n`;
        if (onDone) {
          code += "var _done = () => {\n";
          code += onDone();
          code += "};\n";
        }
        for (let i = 0; i < this.options.taps.length; i++) {
          const done = () => {
            if (onDone) return "if(--_counter === 0) _done();\n";
            else return "--_counter;";
          };
          const doneBreak = skipDone => {
            if (skipDone || !onDone) return "_counter = 0;\n";
            else return "_counter = 0;\n_done();\n";
          };
          code += "if(_counter <= 0) break;\n";
          code += onTap(
            i,
            () =>
              this.callTap(i, {
                onError: error => {
                  let code = "";
                  code += "if(_counter > 0) {\n";
                  code += onError(i, error, done, doneBreak);
                  code += "}\n";
                  return code;
                },
                onResult:
                  onResult &&
                  (result => {
                    let code = "";
                    code += "if(_counter > 0) {\n";
                    code += onResult(i, result, done, doneBreak);
                    code += "}\n";
                    return code;
                  }),
                onDone:
                  !onResult &&
                  (() => {
                    return done();
                  }),
                rethrowIfPossible
              }),
            done,
            doneBreak
          );
        }
        code += "} while(false);\n";
        return code;
      }
    
      // 将构造时传入的函数进行重组,并转化为参数字符串形势
      args({ before, after } = {}) {
        let allArgs = this._args;
        if (before) allArgs = [before].concat(allArgs);
        if (after) allArgs = allArgs.concat(after);
        if (allArgs.length === 0) {
          return "";
        } else {
          return allArgs.join(", ");
        }
      }
    
      // 获取注册回调函数
      getTapFn(idx) {
        return `_x[${idx}]`;
      }
    
      // 获取注册事件
      getTap(idx) {
        return `_taps[${idx}]`;
      }
    
      // 获取拦截器
      getInterceptor(idx) {
        return `_interceptors[${idx}]`;
      }
    }
    
    module.exports = HookCodeFactory;
    

    看到这里我想应该对 tapable 的流程应该很清楚了,每个 Hook 只不过是重写了 content 这个方法,然后再根据需求去调用顺序执行,或者循环执行满足条件再执行下一个,或者是并发去执行,这些不同,都会将这些方法的函数先生成出来然后在调用,每一个都分析篇幅太长,我直接将几个有代表性的生成的触发方法贴出来以供参考

    // 生成这种函数遍历每一个注册的回调函数遇到返回不是undefined的就直接返回结果,不继续往下执行
    function SyncBailHook(arg1, arg2) {
      "use strict";
      var _context;
      var _x = this._x;
      var _fn0 = _x[0];
      var _result0 = _fn0(arg1, arg2);
      if (_result0 !== undefined) {
        return _result0;
      } else {
        var _fn1 = _x[1];
        var _result1 = _fn1(arg1, arg2);
        if (_result1 !== undefined) {
          return _result1;
        } else {
        }
      }
    }
    
    /**
     * 生成的callAsync函数,顺序执行每个注册方法,都执行完成调用
     * @param {*} arg1
     * @param {*} _callback
     */
    function AsyncSeriesHook(arg1, _callback) {
      "use strict";
      var _context;
      var _x = this._x;
      function _next0() {
        var _fn1 = _x[1];
        _fn1(arg1, _err1 => {
          if (_err1) {
            _callback(_err1);
          } else {
            _callback();
          }
        });
      }
      var _fn0 = _x[0];
      _fn0(arg1, _err0 => {
        if (_err0) {
          _callback(_err0);
        } else {
          _next0();
        }
      });
    }
    
    /**
     * 异步并行执行注册事件,如果哪个事件先完成,将参数传给call的回调函数
     * @param {*} arg1
     * @param {*} _callback
     */
    function AsyncParallelHook(arg1, _callback) {
      "use strict";
      var _context;
      var _x = this._x;
      do {
        var _counter = 2;
        var _done = () => {
          _callback();
        };
        if (_counter <= 0) break;
        var _fn0 = _x[0];
        _fn0(arg1, _err0 => {
          if (_err0) {
            if (_counter > 0) {
              _callback(_err0);
              _counter = 0;
            }
          } else {
            if (--_counter === 0) _done();
          }
        });
        if (_counter <= 0) break;
        var _fn1 = _x[1];
        _fn1(arg1, _err1 => {
          if (_err1) {
            if (_counter > 0) {
              _callback(_err1);
              _counter = 0;
            }
          } else {
            if (--_counter === 0) _done();
          }
        });
      } while (false);
    }
    

    相关文章

      网友评论

        本文标题:webpack学习(四)源码之tapable

        本文链接:https://www.haomeiwen.com/subject/dfunuctx.html