tapable 的作用类似与 EventEmitter ,将内部事件分发到对应的 plugin 上。

Hook

tapable 提供了多种 hook,根据返回值可以分为下面几类:

  • Basic:按顺序调用 tap 的函数
  • Waterfall:和上面类似,不过会把上一个函数的返回值传给下一个函数
  • Bail:可以提前退出,当某个函数返回任意值以后
  • Loop:当某个函数返回非 undefined 后,重新从第一个函数执行,直到所有函数返回 undefined

hook 还可以是同步或者异步(异步意思是只有在用户 callback 执行了以后才继续下一个,参考下面的 async 的代码)的:

  • Sync:这种 hook 只提供 tap() 方法
  • AsyncSeries:提供 tap() , tapAsync() , tapPromise() 三种方法,回调函数是按同步顺序执行的
  • AsyncParallel:提供 tap() , tapAsync() , tapPromise() 三种方法,回调函数是按并行执行的

现在通过名称来看看 AsyncSeriesWaterfallHook ,就知道它们是什么意思了。

例子

  1. const {SyncHook, AsyncParallelHook} = require('tapable')
  2. class Car {
  3. constructor() {
  4. this.hooks = {
  5. accelerate: new SyncHook(["newSpeed"]),
  6. brake: new SyncHook(),
  7. calculateRoutes: new AsyncParallelHook(
  8. ["source", "target", "routesList"]
  9. )
  10. }
  11. }
  12. setSpeed(newSpeed) {
  13. // 通过 call 来触发回调
  14. this.hooks.accelerate.call(newSpeed)
  15. }
  16. }
  17. const myCar = new Car()
  18. // 通过 tap 来注册回调
  19. myCar.hooks.brake.tap("WarningLampPlugin", () => console.log('warning lamp.on()'));
  20. myCar.setSpeed("100km/h")

源码

源代码里面用了一些技巧来生成代码,这个不是很关注。下面来看一下具体每个 hooks 是怎么执行的

SyncHook

这个应该算是最基本的 hook 了,假设我们注册了 2 个 SyncHook

myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => 
                           console.log(`Accelerated to ${newSpeed}`));
myCar.hooks.accelerate.tap("ErrorLampPlugin", () => console.log('warning lamp.on()'));

那么 call 的时候生成的函数就是下面这个

function anonymous(newSpeed) {
  "use strict";
  var _context;
  var _x = this._x;
  var _fn0 = _x[0];
  _fn0(newSpeed);
  var _fn1 = _x[1];
  _fn1(newSpeed);
}

就是按顺序调用注册的回调函数

SyncWaterfallHook

和 SyncHook 的区别就是输入为前一个回调的返回值

function anonymous(newSpeed) {
  "use strict";
  var _context;
  var _x = this._x;
  var _fn0 = _x[0];
  var _result0 = _fn0(newSpeed);
  if (_result0 !== undefined) {
    newSpeed = _result0;
  }
  var _fn1 = _x[1];
  var _result1 = _fn1(newSpeed);
  if (_result1 !== undefined) {
    newSpeed = _result1;
  }
  return newSpeed;
}

SyncBailHook

一旦返回值不为 undefined ,那么直接退出

function anonymous(newSpeed) {
  "use strict";
  var _context;
  var _x = this._x;
  var _fn0 = _x[0];
  var _result0 = _fn0(newSpeed);
  if (_result0 !== undefined) {
    return _result0;
    ;
  } else {
    var _fn1 = _x[1];
    var _result1 = _fn1(newSpeed);
    if (_result1 !== undefined) {
      return _result1;
      ;
    } else {
    }
  }
}

AsyncSeriesWaterfallHook

注册两个回调

myCar.hooks.calculateRoutes.tapAsync("BingMapPlugin", (source, target, routesList, callback) => {
    console.log('bing find', source, target, routesList);
    routesList.push({ address: 'aaaa Inc' });
    callback();
});

myCar.hooks.calculateRoutes.tapAsync("GoogleMapPlugin", (source, target, routesList, callback) => {
    console.log('google find', source, target, routesList);
    routesList.push({ address: 'google Inc' });
    callback();
});

看看生成了什么代码

function anonymous(source, target, routesList, _callback
) {
  "use strict";
  var _context;
  var _x = this._x;
  function _next0() {
    var _fn1 = _x[1];
    _fn1(source, target, routesList, (function (_err1, _result1) {
      if (_err1) {
        _callback(_err1);
      } else {
        if (_result1 !== undefined) {
          source = _result1;
        }
        _callback(null, source);
      }
    }));
  }
  var _fn0 = _x[0];
  _fn0(source, target, routesList, (function (_err0, _result0) {
    if (_err0) {
      _callback(_err0);
    } else {
      if (_result0 !== undefined) {
        source = _result0;
      }
      _next0();
    }
  }));
}

AsyncParallelHook

看看怎么实现并行的

function anonymous(source, target, routesList, _callback) {
  "use strict";
  var _context;
  var _x = this._x;
  do {
    var _counter = 2;
    var _done = (function () {
      _callback();
    });
    if (_counter <= 0) break;
    var _fn0 = _x[0];
    _fn0(source, target, routesList, (function (_err0) {
      if (_err0) {
        if (_counter > 0) {
          _callback(_err0);
          _counter = 0;
        }
      } else {
        if (--_counter === 0) _done();
      }
    }));
    if (_counter <= 0) break;
    var _fn1 = _x[1];
    _fn1(source, target, routesList, (function (_err1) {
      if (_err1) {
        if (_counter > 0) {
          _callback(_err1);
          _counter = 0;
        }
      } else {
        if (--_counter === 0) _done();
      }
    }));
  } while (false);

}

哦,就是每次在结束回调里判断计数器有没有为0