前言
所有 Hook 都提供了额外的拦截器 API。
本篇来看一下拦截器的用法。
Interception 拦截器
call
类型:(...args) => void
此方法会在定义的 Hook 被调用之前执行。也就是 call、callAsync 和 promise 方法之前执行。
tap
类型:(tap: Tap) => void
此方法会在订阅函数执行之前执行。
loop
类型:(...args) => void
此方法会在 具有 loop 功能的 Hook 的订阅函数执行之前执行。
register
类型: (tap: Tap) => Tap | undefined
此方法会在每个订阅函数之前执行,并且可以用于修改订阅函数的 Tap 对象。
这个待分析源码时,就能清晰明白它的功能了。
示例
示例代码
用一个示例看一下拦截器功能:
const {
AsyncSeriesLoopHook
} = require('tapable');
const hook = new AsyncSeriesLoopHook(['name', 'age']);
let count1 = 0;
let count2 = 0;
hook.intercept({
call: () => {
console.log('call');
},
tap: tapInfo => {
console.log('tap', tapInfo);
},
register: tapInfo => {
console.log('register', tapInfo);
return tapInfo;
},
loop: () => {
console.log('intercept loop');
}
});
hook.tapAsync('js', (name, age, callback) => {
if (++count1 >= 2) {
console.log('js', name, age, count1);
callback();
} else {
console.log('js', name, age, count1);
callback(null, 'js1');
}
});
hook.tapAsync('node', (name, age, callback) => {
if (++count2 >= 2) {
console.log('node', name, age, count2);
callback();
} else {
console.log('node', name, age, count2);
callback(null, 'node2');
}
});
hook.callAsync('naonao', 2, err => {
console.log('end', err);
});
打印结果
register { type: 'async', fn: [Function (anonymous)], name: 'js' }
register { type: 'async', fn: [Function (anonymous)], name: 'node' }
call
intercept loop
tap { type: 'async', fn: [Function (anonymous)], name: 'js' }
js naonao 2 1
intercept loop
tap { type: 'async', fn: [Function (anonymous)], name: 'js' }
js naonao 2 2
tap { type: 'async', fn: [Function (anonymous)], name: 'node' }
node naonao 2 1
intercept loop
tap { type: 'async', fn: [Function (anonymous)], name: 'js' }
js naonao 2 3
tap { type: 'async', fn: [Function (anonymous)], name: 'node' }
node naonao 2 2
end undefined
打印 hook.callAsync
打印此时的 hook.callAsync 方法:
function anonymous(name, age, _callback) {
"use strict";
var _context;
var _x = this._x;
var _taps = this.taps;
var _interceptors = this.interceptors;
_interceptors[0].call(name, age);
var _looper = (function () {
var _loopAsync = false;
var _loop;
do {
_loop = false;
_interceptors[0].loop(name, age);
function _next0() {
var _tap1 = _taps[1];
_interceptors[0].tap(_tap1);
var _fn1 = _x[1];
_fn1(name, age, (function (_err1, _result1) {
if (_err1) {
_callback(_err1);
} else {
if (_result1 !== undefined) {
_loop = true;
if (_loopAsync) _looper();
} else {
if (!_loop) {
_callback();
}
}
}
}));
}
var _tap0 = _taps[0];
_interceptors[0].tap(_tap0);
var _fn0 = _x[0];
_fn0(name, age, (function (_err0, _result0) {
if (_err0) {
_callback(_err0);
} else {
if (_result0 !== undefined) {
_loop = true;
if (_loopAsync) _looper();
} else {
_next0();
}
}
}));
} while (_loop);
_loopAsync = true;
});
_looper();
}
这里先忽略 register ,放在下篇源码分析的时候再看它的具体执行过程。
从代码中可以看到:
- 拦截器的 call (代码中的 _interceptors[0].call ) 是最先执行的,而且只执行了一次,它接收的参数就是 Hook 调用时传递的参数
- 拦截器的 loop (代码中的 _interceptors[0].loop) 是在每次循环时执行的,它接收的参数就是 Hook 调用时传递的参数
- 拦截器的 tap (代码中的 _interceptors[0].tap),是在每个订阅函数执行前执行的,它接收的参数是 Tap 对象。
Context 对象
插件和拦截器可以选择访问可选的 context 对象,该对象可用于将任意值传递给后续插件和拦截器。
示例
示例代码
const {
AsyncSeriesLoopHook
} = require('tapable');
const hook = new AsyncSeriesLoopHook(['name', 'age']);
hook.intercept({
context: true,
tap: (context, tapInfo) => {
console.log('tap', tapInfo);
if (context) {
context.show = true;
}
}
});
hook.tapAsync('js', (name, age, callback) => {
console.log('js', name, age);
callback();
});
hook.tapAsync({
context: true,
name: 'css'
}, (context, name, age, callback) => {
if (context && context.show) {
console.log('css context', name, age);
callback();
} else {
console.log('css', name, age);
callback();
}
});
hook.tapAsync('node', (name, age, callback) => {
console.log('node', name, age);
callback();
});
hook.callAsync('naonao', 2, err => {
console.log('end', err);
});
打印结果
tap { type: 'async', fn: [Function (anonymous)], name: 'js' }
js naonao 2
tap {
type: 'async',
fn: [Function (anonymous)],
context: true,
name: 'css'
}
css context naonao 2
tap { type: 'async', fn: [Function (anonymous)], name: 'node' }
node naonao 2
end undefined
从打印结果看出,第二个订阅函数使用了 context,它能获取到 context.show 属性,所以打印出了 css context naonao 2。
这样就可以通过 context 向下传递一些值。
但是在最新版本的 Tapable 已经开始提示 DeprecationWarning: Hook.context is deprecated and will be removed。 所以后面的版本会移除 context。
打印 hook.tapAsync
打印此时的 hook.tapAsync 方法:
function anonymous(name, age, _callback) {
"use strict";
var _context = {};
var _x = this._x;
var _taps = this.taps;
var _interceptors = this.interceptors;
var _looper = (function () {
var _loopAsync = false;
var _loop;
do {
_loop = false;
function _next1() {
var _tap2 = _taps[2];
_interceptors[0].tap(_context, _tap2);
var _fn2 = _x[2];
_fn2(name, age, (function (_err2, _result2) {
if (_err2) {
_callback(_err2);
} else {
if (_result2 !== undefined) {
_loop = true;
if (_loopAsync) _looper();
} else {
if (!_loop) {
_callback();
}
}
}
}));
}
function _next0() {
var _tap1 = _taps[1];
_interceptors[0].tap(_context, _tap1);
var _fn1 = _x[1];
_fn1(_context, name, age, (function (_err1, _result1) {
if (_err1) {
_callback(_err1);
} else {
if (_result1 !== undefined) {
_loop = true;
if (_loopAsync) _looper();
} else {
_next1();
}
}
}));
}
var _tap0 = _taps[0];
_interceptors[0].tap(_context, _tap0);
var _fn0 = _x[0];
_fn0(name, age, (function (_err0, _result0) {
if (_err0) {
_callback(_err0);
} else {
if (_result0 !== undefined) {
_loop = true;
if (_loopAsync) _looper();
} else {
_next0();
}
}
}));
} while (_loop);
_loopAsync = true;
});
_looper();
}
从代码中可以看出,首先定义了 var _context = {} ,然后在拦截器执行 tap 方法时,会把这个 _context 当作第一个参数传递进去。
对于订阅函数,如果设置了 context: true,在调用时就会把 _context 当作第一个参数传递进去;否则就不传递。
结语
本篇涉及的是拦截器和 Context 对象。
下篇我们将从源码说起,揭开 Tapable 的面纱。
更多精彩,请关注微信公众号:闹闹前端