tapable源码解读

387 阅读4分钟

tapable 是一个类似于 nodejs 的 EventEmitter 的库,主要是提供钩子函数的发布与订阅,webpack 中的许多对象都扩展自 Tapable 类。

Tapable 包暴露了许多 Hook 类,它们可以用来为插件创建钩子。

image.png

介绍

Hook 程序会编译一个最有效的方法来运行你的插件,它生成代码取决于:

  • 注册插件的数量(没有、一个、多个)
  • 注册的插件类型(sync、 async、 promise)
  • 使用的调用方法(sync、 async、 promise)
  • 参数的数量
  • 是否使用拦截器 这样确保了最快的执行速度。

所有 hook 构造函数都带有一个可选参数,这是一个作为字符串的参数名列表。

const hook = new SyncHook(["arg1", "arg2", "arg3"]);

Hook 的类型

  • Basic hook:简单的调用通过 tap 注册的函数。
  • Bail:当任何一个通过 tap 注册的函数返回一个 non-undefined 的值时,将停止执行剩余的函数。
  • Waterfall:将所有通过 tap 注册的函数链起来,并将每个函数的返回值传递给下一个函数。
  • loop:当任何一个通过 tap 注册的函数返回一个 non-undefined 的值时,将重新返回到从第一个注册的函数开始执行,一直循环,直到所有的函数都返回 undefined。 此外,hook 可以是同步或异步的,为了反映这一点,有三种类型 Sync(同步),AsyncSeries(异步串行),AsyncParallel(异步并行)

拦截器

所有 hook 都提供了额外的拦截 API

hookInstance.intercept({
    call(...args) => void, // call hook
    tap: (tap: Tap) => void, // tap hook
    loop: (...args) => void, // loop hook
    register: (tap: Tap) => Tap | undefined, // 可以修改每个 tap
})

示例

const hookObj = {
  SyncHook,
  SyncBailHook,
  SyncWaterfallHook,
  SyncLoopHook,
  AsyncParallelHook,
  AsyncParallelBailHook,
  AsyncSeriesHook,
  AsyncSeriesBailHook,
  AsyncSeriesLoopHook,
  AsyncSeriesWaterfallHook,
}

同步示例

设置计数器为6,tap${n} 执行后减去 n,其中 tap2 有返回值,当 num >= 0 时返回 arg1 和 arg2 的拼接字符串 ${arg1}${arg2},其余函数没有返回值

const testSyncHook = (hookName) => {
  let num = 6;
  const hookInstance = new hookObj[hookName](['arg1', 'arg2']);

  hookInstance.tap('tap1', (arg1, arg2) => {
    num = num - 1;
    console.log('tap1', arg1, arg2, num);
  });

  hookInstance.tap('tap2', (arg1, arg2) => {
    num = num - 2;
    console.log('tap2', arg1, arg2, num);
    return num <= 0 ? undefined : `${arg1}&${arg2}`;
  });

  hookInstance.tap('tap3', (arg1, arg2) => {
    num = num - 3;
    console.log('tap3', arg1, arg2, num)
  });

  hookInstance.call('x', 'y');
  console.log('<- ----- END ----- ->\n\n')
}
SyncHook
testSyncHook('SyncHook');

基础的 hook。输出结果按照注册的顺序输出,且 num 最后为 0

image.png

SyncBailHook
testSyncHook('SyncBailHook');

可中断的 hook。tap2 执行后返回值为 'x&y' 不是 undefined 所以停止执行后续的函数

image.png

SyncWaterfallHook
testSyncHook('SyncWaterfallHook');

同步瀑布 hook,函数执行后在有返回值的情况下,该返回值会作为下一个注册函数的参数传入。可以注册多个函数来对结果进行加工;tap3 的第一个参数变成 tap2 的返回值 'x&y'

image.png

SyncLoopHook
testSyncHook('SyncLoopHook');

可循环的 hook,如果存在返回值不为 undefined(不能是 null 或者 '')的情况会一直循环,直到所有返回值为 undefined 才结束。tap2 第一次执行后返回 'x&y',重新回到第一个函数开始执行,第二次 tap2 执行后返回 undefined 后,继续执行后续函数

image.png

异步示例

要正常使用异步 Hook 类,需要将 tap 和 call 都对应使用异步。

AsyncParallelHook

异步并行 hook,当所有的异步任务都执行完之后最后执行 callAsync 或者 promise 中的函数

console.time('time')
const asyncParallelHook = new AsyncParallelHook(['arg1', 'arg2']);
asyncParallelHook.tapAsync('tap1', (arg1, arg2, next) => {
  setTimeout(() => {
    console.log('tap1', arg1, arg2);
    next();
  }, 3000)
})
asyncParallelHook.tapAsync('tap2', (arg1, arg2, next) => {
  setTimeout(() => {
    console.log('tap2', arg1, arg2);
    next();
  }, 2000)
})
asyncParallelHook.tapAsync('tap3', (arg1, arg2, next) => {
  setTimeout(() => {
    console.log('tap3', arg1, arg2);
    next();
  }, 1000)
})

asyncParallelHook.callAsync('x', 'y', () => {
  console.log('done!')
  console.timeEnd('time')
})

image.png

同时开始执行异步任务,各自完成各自的任务,等到所有任务都完成之后执行最后回调任务。

AsyncSeriesHook

异步串行 hook,按注册顺序执行所有的异步任务

console.time('time')
const asyncSeriesHook = new AsyncSeriesHook(['arg1', 'arg2']);
asyncSeriesHook.tapAsync('tap1', (arg1, arg2, next) => {
  setTimeout(() => {
    console.log('tap1', arg1, arg2);
    next();
  }, 3000)
})
asyncSeriesHook.tapAsync('tap2', (arg1, arg2, next) => {
  setTimeout(() => {
    console.log('tap2', arg1, arg2);
    next();
  }, 2000)
})
asyncSeriesHook.tapAsync('tap3', (arg1, arg2, next) => {
  setTimeout(() => {
    console.log('tap3', arg1, arg2);
    next();
  }, 1000)
})

asyncSeriesHook.callAsync('x', 'y', () => {
  console.log('done!')
  console.timeEnd('time')
})

image.png

执行结果会按照注册的顺序逐步开始执行,上一个任务完成后才开始下一个任务,所有任务完成之后执行最后回调任务。相当于是将异步任务进行了同步执行

AsyncParallelBailHook

异步并行可中断 hook,当存在返回值时就中断任务,执行回调

console.time('time')
const asyncParallelBailHook = new AsyncParallelBailHook(['arg1', 'arg2']);
asyncParallelBailHook.tapAsync('tap1', (arg1, arg2, callback) => {
  setTimeout(() => {
    console.log('tap1', arg1, arg2);
    callback(undefined, undefined);
  }, 3000)
})
asyncParallelBailHook.tapAsync('tap2', (arg1, arg2, callback) => {
  setTimeout(() => {
    console.log('tap2', arg1, arg2);
    callback(undefined, 'return tap2');
  }, 2000)
})
asyncParallelBailHook.tapAsync('tap3', (arg1, arg2, callback) => {
  setTimeout(() => {
    console.log('tap3', arg1, arg2);
    callback(undefined, 'return tap3');
  }, 1000)
})

asyncParallelBailHook.callAsync('x', 'y', (err, result) => {
  console.log('done!')
  console.log(err, result)
  console.timeEnd('time')
})

image.png

因为是异步并发,中断并不能取消,而是仅使用中断那个任务的结果,而且是按照注册的顺序取中断结果,示例中的 tap3 最先执行完而且也有返回值,但是 tap2 优先于 tap3 注册,所以最后回调结果中用的是 tap2 的返回值;需要等到所有任务完成之后才能按照注册顺序看是否有返回,所以耗时是最大任务的耗时:3 秒

AsyncSeriesBailHook

异步串行可中断 hook,当存在返回值时就中断任务,执行回调

console.time('time')
const asyncSeriesBailHook = new AsyncSeriesBailHook(['arg1', 'arg2']);
asyncSeriesBailHook.tapAsync('tap1', (arg1, arg2, callback) => {
  setTimeout(() => {
    console.log('tap1', arg1, arg2);
    callback(undefined, undefined);
  }, 3000)
})
asyncSeriesBailHook.tapAsync('tap2', (arg1, arg2, callback) => {
  setTimeout(() => {
    console.log('tap2', arg1, arg2);
    callback(undefined, 'return tap2');
  }, 2000)
})
asyncSeriesBailHook.tapAsync('tap3', (arg1, arg2, callback) => {
  setTimeout(() => {
    console.log('tap3', arg1, arg2);
    callback(undefined, 'return tap3');
  }, 1000)
})

asyncSeriesBailHook.callAsync('x', 'y', (err, result) => {
  console.log('done!')
  console.log(err, result)
  console.timeEnd('time')
})

image.png

由于是串行,按照注册顺序排队执行,遇到中断时就可以取消后续操作

AsyncSeriesLoopHook

异步串行可循环 hook,和 SyncLoopHook 功能一样,只是可以执行异步任务

let num = 6;
console.time('time')
const asyncSeriesLoopHook = new AsyncSeriesLoopHook(['arg1', 'arg2']);
asyncSeriesLoopHook.tapAsync('tap1', (arg1, arg2, callback) => {
  setTimeout(() => {
    console.log('tap1', arg1, arg2);
    num -= 1;
    callback(undefined, undefined);
  }, 3000)
})
asyncSeriesLoopHook.tapAsync('tap2', (arg1, arg2, callback) => {
  setTimeout(() => {
    console.log('tap2', arg1, arg2);
    num -= 1;
    callback(undefined, undefined);
  }, 2000)
})
asyncSeriesLoopHook.tapAsync('tap3', (arg1, arg2, callback) => {
  setTimeout(() => {
    console.log('tap3', arg1, arg2);
    num -= 1;
    callback(undefined, num <= 0 ? undefined : 'return tap3');
  }, 1000)
})

asyncSeriesLoopHook.callAsync('x', 'y', (err, result) => {
  console.log('done!')
  console.log(err, result)
  console.timeEnd('time')
})

image.png

需要等待每个异步任务完成后再开始下一个,tap3 第一次返回不为 undefined,第二次为 undefined,总耗时:12秒

AsyncSeriesWaterfallHook

异步串行瀑布 hook,和 SyncWaterfallHook 功能一样,只是可以执行异步任务

console.time('time')
const asyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook(['arg1', 'arg2']);
asyncSeriesWaterfallHook.tapAsync('tap1', (arg1, arg2, callback) => {
  setTimeout(() => {
    console.log('tap1', arg1, arg2);
    callback(undefined, undefined);
  }, 3000)
})
asyncSeriesWaterfallHook.tapAsync('tap2', (arg1, arg2, callback) => {
  setTimeout(() => {
    console.log('tap2', arg1, arg2);
    callback(undefined, `${arg1}&${arg2}`);
  }, 2000)
})
asyncSeriesWaterfallHook.tapAsync('tap3', (arg1, arg2, callback) => {
  setTimeout(() => {
    console.log('tap3', arg1, arg2);
    callback(undefined, 'return tap3');
  }, 1000)
})

asyncSeriesWaterfallHook.callAsync('x', 'y', (err, result) => {
  console.log('done!')
  console.log(err, result)
  console.timeEnd('time')
})

image.png

源码分析

源码中所有的 hook 都是基于 Hook 类 和 HookCodeFactory 类来实现的,主要分析这两个类即可

Hook.js

constructor
const CALL_DELEGATE = function (...args) {
  this.call = this._createCall("sync");
  return this.call(...args);
};
const CALL_ASYNC_DELEGATE = function (...args) {
  this.callAsync = this._createCall("async");
  return this.callAsync(...args);
};
const PROMISE_DELEGATE = function (...args) {
  this.promise = this._createCall("promise");
  return this.promise(...args);
};
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;
}

compile(options) {
  throw new Error("Abstract: should be overridden");
}

_createCall(type) {
  return this.compile({
    taps: this.taps,
    interceptors: this.interceptors,
    args: this._args,
    type: type
  });
}
  • 构造函数中记录了参数名称数组(args),注册的 tap 数组(taps),拦截器数组(interceptors)
  • call、callAsync、promise 分别指向了函数 *_DELEGATE,这几个函数都调用了需要重新实现的 compile 函数。在其他 ****Hook 类中都重写了 compile 函数,重写的函数中调用了 HookCodeFactory 类中的方法来动态生成 call、callAsync、promise 的函数体
tap
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(type, options, fn) {
  if (typeof options === "string") {
    options = {
      name: options.trim()
    };
  } 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");
  }
  options = Object.assign({ type, fn }, options);
  options = this._runRegisterInterceptors(options);
  this._insert(options);
}

_runRegisterInterceptors(options) {
  for (const interceptor of this.interceptors) {
    if (interceptor.register) {
      const newOptions = interceptor.register(options);
      if (newOptions !== undefined) {
        options = newOptions;
      }
    }
  }
  return options;
}
  • tap、tapAsync、tapPromise 最终都调用内部的 _tap 函数,通过传递不同的 type 区分,这里生成的 options 为 { type, name, fn },这个 options 就是属性 taps 中的一个值(在 this._insert(options) 中可以看到)
  • 在执行 this._insert(options) 之前调用了 this._runRegisterInterceptors(options) ,说明每次在注册函数时遍历了所有的拦截器,并调用其中的 register 方法来重新处理当前注册的这个 options(即:tap[ i ] )
_insert
_resetCompilation() {
  this.call = this._call;
  this.callAsync = this._callAsync;
  this.promise = this._promise;
}

_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;
}

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]);
    }
  }
}
  • _insert、intercept 都对 this.call、this.callAsync、this.promise 重新赋值(this._resetCompilation()),使这几个函数等于初始的 *_DELEGATE ,这样做是因为在每次 interceptors、taps 的值有插入时都需要重新生成新的触发函数(call/callAsync/promise),才能在实体调用这些函数时实现例子中的链式触发、循环触发等需求,所以只要不新增 interceptors 或 taps 中的值,call/callAsync/promise 的函数体就不会发生变化
  • _insert 函数主要是将传入的 options 添加到 taps 中,通过 options 中的 before/stage 字段来调整在 taps 中存放的位置

HookCodeFactory.js

整个 HookCodeFactory 类里面使用 Function 构造函数来生成 Function 对象(即函数),语法:

new Function ([arg1[, arg2[, ...argN]],] functionBody)
  • arg1, arg2, ... argN:被函数使用的参数的名称必须是合法命名的。参数名称是一个有效的JavaScript标识符的字符串,或者一个用逗号分隔的有效字符串的列表;例如 “×”,“theValue”,或 “a, b”。
  • functionBody:一个含有包括函数定义的 JavaScript 语句的字符串。 由 Function 构造器创建的函数不会创建当前环境的闭包,它们总是被创建于全局环境,因此在运行时它们只能访问全局变量和自己的局部变量,不能访问它们被 Function 构造器创建时所在的作用域的变量。
const fn1 = () => {
  const v1 = 'fn1';
  return function () {
    return v1;
  }
}

const fn2 = () => {
  const v2 = 'fn2';
  return new Function('return v2;');
}

console.log(fn1()()); // fn1
console.log(fn2()()); // v2 is not defined

const adder = new Function("a", "b", "c", "return a + b + c");
adder(2, 6, 8); // 16

在 HookCodeFactory 中核心的几个函数如下

class HookCodeFactory {
  contentWithInterceptors(options) { }
  callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) { }
  callTapsSeries({ onError, onResult, resultReturns, onDone, doneReturns, rethrowIfPossible }) { }
  callTapsLooping({ onError, onDone, rethrowIfPossible }) { }
  callTapsParallel({ onError, onResult, onDone, rethrowIfPossible, onTap = (i, run) => run() }) { }
}
  • callTap:基础执行 tap 任务
  • callTapsSeries:串行执行
  • callTapsParallel:并行执行
  • callTapsLooping:循环执行
COMPILE

SyncHook 类中重新了 compile 方法

const COMPILE = function (options) {
  factory.setup(this, options);
  return factory.create(options);
};

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;
}
  • 其他 Hook 子类中具体的 compile 实现基本都使用如上所示的 COMPILE 函数,他们有不同的 factory 实例,这些实例的构造函数都继承至 HookCodeFactory 类,但分别实现了自己的 content 函数,这些 content 函数调用父类的 callTapsSeries、callTapsParallel、callTapsLooping 函数来生成不同的函数体
  • factory.setup 将 Hook 基类构造函数中的 this._x = undefined; 重新赋值为所有通过 tap 注册的 fn 数组
  • factory.create 生成最终的 call、callAsync、promise 函数
contentWithInterceptors
...
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(", ");
  }
}

header() {
  let code = "";
  code += "var _context;\n";
  code += "var _x = this._x;\n";
  if (this.options.interceptors.length > 0) {
    code += "var _taps = this.taps;\n";
    code += "var _interceptors = this.interceptors;\n";
  }
  return code;
}

contentWithInterceptors(options) {
  if (this.options.interceptors.length > 0) {
    const onError = options.onError;
    const onResult = options.onResult;
    const onDone = options.onDone;
    let code = "";
    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()});\n`;
      }
    }
    code += this.content(
      Object.assign(options, {
        onError:
          onError &&
          (err => {
            let code = "";
            for (let i = 0; i < this.options.interceptors.length; i++) {
              const interceptor = this.options.interceptors[i];
              if (interceptor.error) {
                code += `${this.getInterceptor(i)}.error(${err});\n`;
              }
            }
            code += onError(err);
            return code;
          }),
        onResult:
          onResult &&
          (result => {
            let code = "";
            for (let i = 0; i < this.options.interceptors.length; i++) {
              const interceptor = this.options.interceptors[i];
              if (interceptor.result) {
                code += `${this.getInterceptor(i)}.result(${result});\n`;
              }
            }
            code += onResult(result);
            return code;
          }),
        onDone:
          onDone &&
          (() => {
            let code = "";
            for (let i = 0; i < this.options.interceptors.length; i++) {
              const interceptor = this.options.interceptors[i];
              if (interceptor.done) {
                code += `${this.getInterceptor(i)}.done();\n`;
              }
            }
            code += onDone();
            return code;
          })
      })
    );
    return code;
  } else {
    return this.content(options);
  }
}

getInterceptor(idx) {
  return `_interceptors[${idx}]`;
}

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
  })
);
...
  • args 方法列出所有参数,通过逗号连接成字符串返回,就像 "a, b, c"
  • header 方法定义了两个变量 _context、_x,并将 this._x 赋值给了 _x,在有拦截器的情况下对 this.taps 和 this.interceptors 两个变量进行了重新赋值
  • contentWithInterceptors 方法的参数是一个 options 对象,对象中有几个方法(onError、onResult、onDone)返回的都是字符串(注意这里的字符串,因为整个构造函数体是以字符串的形式存在的,所有的其他可执行代码都是为了生成最终的函数体片段),例如下面这段代码可以打印出数字 101~110
    const baseV = 50;
    let code = '';
    code += 'for(let i=1; i<=10; i++) { console.log(';
    code += baseV * 2;
    code += ' + i);}'
    const fn = new Function(code);
    fn();
    
    注意:code += baseV * 2; 不能写成 code += 'baseV * 2';,因为作用域的问题拿不到 baseV,实际上 code 的代码为'for(let i=1; i<=10; i++) { console.log(100 + i);}'而不是'for(let i=1; i<=10; i++) { console.log(baseV * 2 + i);}'
  • 当没有拦截器时直接调用子类的 content 方法(将 options 作为参数传入);存在拦截器时用遍历的方式生成所有的拦截器的 call 方法的调用,然后重新遍历生成新的 options 后再调用 content 方法,假设有3个拦截器,初始化的参数为 'arg1, arg2, arg3' 这部分代码生成的代码段相当于如下代码(每次 error、result、done 调用前需要判断是否存在,下面代码默认都存在):
    _interceptors[0].call(arg1, arg2, arg3);
    _interceptors[1].call(arg1, arg2, arg3);
    _interceptors[2].call(arg1, arg2, arg3);
    nextOptions = {
      onError: err => {
        const code = `
        _interceptors[0].error(${err});
        _interceptors[1].error(${err});
        _interceptors[2].error(${err});
        throw ${err};
        `;
        return code;
      },
      onResult: result => {
        const code = `
        _interceptors[0].result(${result});
        _interceptors[1].result(${result});
        _interceptors[2].result(${result});
        return ${result};
        `;
        return code;
      },
      onDone: () => {
        const code = `
        _interceptors[0].done();
        _interceptors[1].done();
        _interceptors[2].done();
        `;
        return code;
      }
    }
    this.content(Object.assign(options, nextOptions));
    

在 HookCodeFactory 的 子类 SyncHookCodeFactory 中的 content 函数如下:

content({ onError, onDone, rethrowIfPossible }) {
  return this.callTapsSeries({
    onError: (i, err) => onError(err),
    onDone,
    rethrowIfPossible
  });
}

对于 SyncHook,只传递了3个参数给 callTapsSeries 函数,而原本 contentWithInterceptors 中的 options 有5个参数,其余的参数在其他 hook 中有用到(如:SyncBailHook 使用了全部参数);同样 onError: (i, err) => onError(err) 中没有用到 i 参数,是因为在 callTapsSeries 函数中调用的时候传递了4个参数(onError(i, error, done, doneBreak)),而这里要接 err参数,使用第一个 i 参数占位。

callTapsSeries

callTapsSeries 是目前遇到的第一个比较核心的函数,可以看到里面调用了 callTap(在 callTapsParallel 里面也调用了这个函数)

callTapsSeries({
  onError,
  onResult,
  resultReturns,
  onDone,
  doneReturns,
  rethrowIfPossible
}) {
  if (this.options.taps.length === 0) return onDone();
  const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");
  const somethingReturns = resultReturns || doneReturns;
  let code = "";
  let current = onDone;
  let unrollCounter = 0;
  for (let j = this.options.taps.length - 1; j >= 0; j--) {
    const i = j;
    const unroll =
      current !== onDone &&
      (this.options.taps[i].type !== "sync" || unrollCounter++ > 20);
    if (unroll) {
      unrollCounter = 0;
      code += `function _next${i}() {\n`;
      code += current();
      code += `}\n`;
      current = () => `${somethingReturns ? "return " : ""}_next${i}();\n`;
    }
    const done = current;
    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)
    });
    current = () => content;
  }
  code += current();
  return code;
}

将 taps 数组做了一个倒序循环,可以看到第一次肯定不会执行if (unroll) {...}里面的内容(因为第一次 current = onDone),而且保证 type !== "sync"(异步注册 tapAsync、tapPromise) 时才会执行,只要执行 if 后就会改变 current,改变之后下一次循环进入的时候 current !== onDone 就为真了,所以可以看出如果全部是异步注册任务,除了第一次 for 循环不执行 if 语句块内容以外,其他后续循环均会执行

在全部是异步 tap 注册的情况下简化代码如下:

callTapsSeries(...) {
    let current = onDone; // AsyncSeriesHook 中 onDone: () => "_callback();\n"
    for (let i = 末尾; i >= 0; i--) {
      if (不是第一次进入) {
        "生成一个 _next_i 的函数,函数体为执行 current 函数"
        current = _next_i();
      }
      const done = current;
      content = this.callTap(i, { onDone: !onResult && done, ...});
      current = () => content;
    }
    code += current();
}

可以看到通过 for 循环不停的对 current 重新赋值和将 current 作为参数传入 callTap 函数,最终可以实现链式调用。

当全部是同步 tap 注册的情况下始终不会执行 if 语句块

callTapsSeries(...) {
    let current = onDone; // SyncHook 中 onDone: () => ""
    for (let i = 末尾; i >= 0; i--) {
      const done = current;
      content = this.callTap(i, { onDone: !onResult && done, ...});
      current = () => content;
    }
    code += current();
}
callTap

在 callTap 中首先使用所有已经注册拦截器中的 tap hook 对当前 tap 进行处理

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`;
  }
}

这段代码中的 code += 'var _tap${tapIndex} = ${this.getTap(tapIndex)};\n;' 这句代码可以提到 for 循环外面后就可以不用使用 hasTapCached 字段了,但是这样的话对于没有 interceptors 的情况下,就会产生提出去的那句无用代码,考虑得十分周到。但是为什么非要声明一个 _tap${tapIndex} 而不是直接传入,而且也不需要 hasTapCached如:

for (let i = 0; i < this.options.interceptors.length; i++) {
  const interceptor = this.options.interceptors[i];
  if (interceptor.tap) {
    code += `${this.getInterceptor(i)}.tap(${interceptor.context ? "_context, " : ""}
    ${this.getTap(tapIndex)});\n`;
  }
}

这里暂时没看懂声明这个变量的特殊用途,猜测是因为异步导致总是拿到最后一个tapIndex

callTap 中对不同 type 分别做了处理,对于 sync 逻辑比较简单,根据不同的 rethrowIfPossible、onResult、onDone 分支处理,里面做了 tap 所注册的 fn 函数的最终调用,对于 async:

code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
const tap = this.options.taps[tapIndex];
case "async":
let cbCode = "";
if (onResult)
  cbCode += `(function(_err${tapIndex}, _result${tapIndex}) {\n`;
else cbCode += `(function(_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;

里面的 cbCode 生成了一个匿名函数通过 this.args 挂到了 fn 的最后一个参数上,这样我们使用时在构造函数中只传递了 N 个参数,但是可以使用 N + 1 个参数,且最后一个参数是个回调函数;cbCode 这个函数里面调用了传递进来的 onDone,在异步 tap 注册中 callTapsSeries 里面生成的 next_i,就是这么巧妙的链在了一起!!!对于有三个 tapAsync 注册的 AsyncSeriesHook 代码生成如下:

function anonymous(arg1, arg2, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  function _next1() {
    var _fn2 = _x[2];
    _fn2(arg1, arg2, function (_err2) {
      if (_err2) {
        _callback(_err2);
      } else {
        _callback();
      }
    });
  }
  function _next0() {
    var _fn1 = _x[1];
    _fn1(arg1, arg2, (function (_err1) {
      if (_err1) {
        _callback(_err1);
      } else {
        _next1();
      }
    }));
  }
  var _fn0 = _x[0];
  _fn0(arg1, arg2, (function (_err0) {
    if (_err0) {
      _callback(_err0);
    } else {
      _next0();
    }
  }));
}

结合上面的 callTapsSeries 和 callTap 函数,第一次 for 循环时 i = 2,不会进入 if 语句,通过 callTap 生成 _next1 的函数体;进入第二次 for 循环时生成完整的 _next1 函数,并将 _next1 传入第二次 callTap,生成 _next0 的函数体(里面调用了 _next1);然后一直循环直到所有结束。这种来回传递参数,最终形成链式调用简直歪瑞古德!promise部分就多个then,catch,逻辑大致差不多。

callTapsParallel
callTapsParallel({ onError, onResult, onDone, rethrowIfPossible, onTap = (i, run) => run() }) {
  let code = "";
  code += "do {\n";
  code += `var _counter = ${this.options.taps.length};\n`;
  if (onDone) {
    code += "var _done = (function() {\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;
}

这段代码的核心在于使用 do { break; }while(false)结构来做全部完成的判断,通过 _counter 作为控制器,每次完成一个异步任务执行之后都减 1 ,然后判断是否完成全部任务;每次循环都加了语句code += "if(_counter <= 0) break;\n";,用于 doneBreak 函数执行后的中断操作。

callTapsLooping
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 = (function() {\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、resultReturns、onDone、doneReturns、rethrowIfPossible的参数可以实现所有的 hook,如果不用这种动态生成函数的方式,每个 tap、call 都由子类去实现的话,会写出很多个函数,而且很多函数还类似。