前言
Tapable 包是 Webpack 插件机制的核心,它暴露出很多 Hook 供插件使用。
在版本 V2.2.0 中,一共提供了 10 种 Hook。
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
复制代码
此外还有两个工具类
- 一个是带有 Hooks 映射的帮助类
exports.HookMap = require("./HookMap");
复制代码
- 一个是可以创建一个 Hook,重定向到其他多个 Hook的帮助类
exports.MultiHook = require("./MultiHook");
复制代码
以上源码都在文件 tapable/lib/index.js 中
Hook 类型
每个 Hook 都可以订阅一个或多个功能,如何运行取决于它们各自的类型。
根据执行流程可以分为以下四种类型:
类型 | 解释 |
---|---|
Basic | 简单的调用它订阅的每一个函数,不关心函数执行的结果 |
Waterfall | 在执行订阅函数时,会把当前函数的返回结果,传递到下一个订阅的函数 |
Bail | 在执行订阅函数时,如果当前订阅函数的返回结果不是 undefined,则后面订阅的函数不会再继续执行 |
Loop | 在执行订阅时,如果当前订阅函数返回的值不是 undefined,它会从第一个订阅的函数开始循环执行 |
根据同异步又可分为以下三种类型:
类型 | 解释 |
---|---|
Sync | 订阅的函数都是同步函数,不能是异步函数,使用 tap 方法订阅 |
AsyncSeries | 异步串行,订阅的函数可以是同步函数或异步函数,使用 tap、tapAsync、tapPromise 方法订阅;顺序执行每个订阅的异步函数 |
AsyncParallel | 异步并行,订阅的函数可以是同步函数或异步函数,使用 tap、tapAsync、tapPromise 方法订阅;并行执行每个订阅的异步函数 |
以上分类反映在其类名中,比如 AsyncSeriesWaterfallHook 就是具有异步串行,并把执行结果传递到下一个订阅函数的功能。
应用示例
下面我们就结合具体的示例,演示一下每一个 Hook 的使用方法。
SyncHook
示例代码:
const {
SyncHook
} = require('tapable');
// 实例化
const hook = new SyncHook(['name', 'age']);
// 订阅
hook.tap('js', (name, age) => {
console.log('js', name, age);
});
// 订阅
hook.tap({ name: 'css' }, (name, age) => {
console.log('css', name, age);
});
// 订阅
hook.tap('node', (name, age) => {
console.log('node', name, age);
});
// 执行订阅函数
hook.call('naonao', 2);
复制代码
执行结果
js naonao 2
css naonao 2
node naonao 2
复制代码
注意
- 构造函数接收一个可选参数,参数类型是一个成员为字符串的数组
- 构造函数接收的参数名,可以任意定义,它只是一个形参,但是数组长度一定要和实际接收的参数保持一致
- 执行 call 函数传递的参数个数一定要等于实例化时接收的数组长度,且一一对应
- call 执行,是根据 tap 订阅的顺序执行即先进先出
改造示例
改造一下上面的示例:
示例代码
const {
SyncHook
} = require('tapable');
// 实例化,参数['aa', 'bb'] 只关心长度,成员的名字无所谓
const hook = new SyncHook(['aa', 'bb']);
// 订阅 name, age有值,因为 call时只传递了两个参数,所以 sex是undefined
hook.tap('js', (name, age, sex) => {
console.log('js', name, age, sex);
});
// 订阅
hook.tap({ name: 'css' }, (name, age) => {
console.log('css', name, age);
});
// 订阅 cc 和 dd 也都是行参,对应的值是和call传递的参数一一对应的
hook.tap('node', (cc, dd) => {
console.log('node', cc, dd);
});
// 执行订阅 参数个数要和实例化时的长度保持一致
hook.call('naonao', 2);
复制代码
执行结果
js naonao 2 undefined
css naonao 2
node naonao 2
复制代码
打印 hook.call
它为什么会是这样的?我们把 hook.call 打印出来就知道了。
function anonymous(aa, bb) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(aa, bb);
var _fn1 = _x[1];
_fn1(aa, bb);
var _fn2 = _x[2];
_fn2(aa, bb);
}
复制代码
这就清晰明了了。
实例化时的数组成员就是 anonymous 函数的形参。
通过 tap 订阅的函数,都分化为 _fn0 、_fn1、_fn2,然后顺序执行它们。
订阅函数执行时,只接收到两个参数,所以第一个订阅函数中的 sex 是 undefined。
所有问题都解释通了。
SyncBailHook
示例代码:
const {
SyncBailHook
} = require('tapable');
const hook = new SyncBailHook(['name', 'age']);
hook.tap('js', (name, age) => {
console.log('js', name, age);
});
hook.tap({ name: 'css' }, (name, age) => {
console.log('css', name, age);
// 返回0 不是undefined,在此结束,后面订阅的函数不会执行
return 0;
});
hook.tap('node', (name, age) => {
console.log('node', name, age);
});
hook.call('naonao', 2);
复制代码
执行结果:
js naonao 2
css naonao 2
复制代码
注意
- 订阅函数的返回值不是 undefined 时,后面的订阅函数就不会执行了
打印 hook.call
打印此时的 hook.call 方法:
function anonymous(name, age) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _result0 = _fn0(name, age);
if (_result0 !== undefined) {
return _result0;
;
} else {
var _fn1 = _x[1];
var _result1 = _fn1(name, age);
if (_result1 !== undefined) {
return _result1;
;
} else {
var _fn2 = _x[2];
var _result2 = _fn2(name, age);
if (_result2 !== undefined) {
return _result2;
;
} else {
}
}
}
}
复制代码
它内部调用订阅函数,并获取订阅函数的执行结果,使用了 !== 运算符对比,所以必须是 undefined时才会继续执行后面的订阅函数。
SyncWaterfallHook
示例代码
const {
SyncWaterfallHook
} = require('tapable');
const hook = new SyncWaterfallHook(['name', 'age']);
hook.tap('js', (name, age) => {
console.log('js', name, age);
});
hook.tap({ name: 'css' }, (name, age) => {
console.log('css', name, age);
// 返回值不是undefined,将作为下一个订阅函数的第一个参数
return `name是${name}`;
});
hook.tap('node', (name, age) => {
console.log('node', name, age);
});
hook.call('naonao', 2);
复制代码
打印结果
js naonao 2
css naonao 2
node name是naonao 2
复制代码
注意
- 如果订阅函数返回的结果不是 undefined,将作为下一个订阅函数的第一个参数
打印 hook.call
打印此时的 hook.call 方法:
function anonymous(name, age) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _result0 = _fn0(name, age);
if (_result0 !== undefined) {
name = _result0;
}
var _fn1 = _x[1];
var _result1 = _fn1(name, age);
if (_result1 !== undefined) {
name = _result1;
}
var _fn2 = _x[2];
var _result2 = _fn2(name, age);
if (_result2 !== undefined) {
name = _result2;
}
return name;
}
复制代码
它内部会判断如果订阅函数的执行结果不是 undefined 会把结果赋值给 name 即第一个参数,然后继续执行后面的订阅函数。
SyncLoopHook
示例代码
const {
SyncLoopHook
} = require('tapable');
const hook = new SyncLoopHook(['name', 'age']);
let count1 = 0;
let count2 = 0;
hook.tap('js', (name, age) => {
console.log('js', name, age);
});
hook.tap({ name: 'css' }, (name, age) => {
console.log('css', name, age, count1);
if (++count1 === 2) {
count1 = 0;
// 返回值是 undefined,才会执行后面的订阅函数
return;
}
// 订阅函数的返回值,不是 undefined,会从第一个订阅函数重新执行
return `name是${name}`;
});
hook.tap('node', (name, age) => {
console.log('node', name, age, count2);
if (++count2 === 2) {
count2 = 0;
return;
}
return 0;
});
hook.call('naonao', 2);
复制代码
打印结果
js naonao 2
css naonao 2 0
js naonao 2
css naonao 2 1
node naonao 2 0
js naonao 2
css naonao 2 0
js naonao 2
css naonao 2 1
node naonao 2 1
复制代码
注意
- 订阅函数的返回值不是 undefined 时,会从第一个订阅函数开始重新执行。
打印 hook.call
打印此时的 hook.call 方法:
function anonymous(name, age) {
"use strict";
var _context;
var _x = this._x;
var _loop;
do {
_loop = false;
var _fn0 = _x[0];
var _result0 = _fn0(name, age);
if (_result0 !== undefined) {
_loop = true;
} else {
var _fn1 = _x[1];
var _result1 = _fn1(name, age);
if (_result1 !== undefined) {
_loop = true;
} else {
var _fn2 = _x[2];
var _result2 = _fn2(name, age);
if (_result2 !== undefined) {
_loop = true;
} else {
if (!_loop) {
}
}
}
}
} while (_loop);
}
复制代码
它内部是一个 do…while 循环,每次执行订阅函数时,如果返回值不是 undefined 就设置 _loop = true 那么循环就会再次执行,所以是从第一个订阅函数开始执行的。
AsyncParallelHook
可以使用 tap、tapAsync 和 tapPromise 方法进行订阅
tap订阅
示例代码
const {
AsyncParallelHook
} = require('tapable');
const hook = new AsyncParallelHook(['name', 'age']);
hook.tap('js', (name, age) => {
// 异步执行
setTimeout(() => {
console.log('js', name, age);
}, 1000);
});
hook.tap({ name: 'css' }, (name, age) => {
// 异步执行
setTimeout(() => {
console.log('css', name, age);
}, 2000);
});
hook.tap('node', (name, age) => {
// 同步执行
console.log('node', name, age);
});
hook.callAsync('naonao', 2, () => {
console.log('end');
});
复制代码
打印结果
node naonao 2
end
js naonao 2
css naonao 2
复制代码
打印的结果中,一秒后打印 js naonao 2 ,两秒后打印 css naonao 2。
注意
- 要使用 hook.callAsync 方法进行调用。
打印 hook.callAsync
打印此时的 hook.callAsync 方法:
function anonymous(name, age, _callback) {
"use strict";
var _context;
var _x = this._x;
do {
var _counter = 3;
var _done = (function () {
_callback();
});
if (_counter <= 0) break;
var _fn0 = _x[0];
var _hasError0 = false;
try {
_fn0(name, age);
} catch (_err) {
_hasError0 = true;
if (_counter > 0) {
_callback(_err);
_counter = 0;
}
}
if (!_hasError0) {
if (--_counter === 0) _done();
}
if (_counter <= 0) break;
var _fn1 = _x[1];
var _hasError1 = false;
try {
_fn1(name, age);
} catch (_err) {
_hasError1 = true;
if (_counter > 0) {
_callback(_err);
_counter = 0;
}
}
if (!_hasError1) {
if (--_counter === 0) _done();
}
if (_counter <= 0) break;
var _fn2 = _x[2];
var _hasError2 = false;
try {
_fn2(name, age);
} catch (_err) {
_hasError2 = true;
if (_counter > 0) {
_callback(_err);
_counter = 0;
}
}
if (!_hasError2) {
if (--_counter === 0) _done();
}
} while (false);
}
复制代码
仔细看一下它内部,先用一个计数器记录订阅函数的个数,顺序执行每一个订阅函数,同时捕获订阅函数,把捕获的错误传递给回调函数;每执行一个订阅函数,就让计数器减一,当所有订阅函数都执行后,才执行回调函数。
tapAsync 订阅
示例代码
const {
AsyncParallelHook
} = require('tapable');
const hook = new AsyncParallelHook(['name', 'age']);
hook.tapAsync('js', (name, age, callback) => {
// 同步
console.log('js', name, age);
callback('js error');
});
hook.tapAsync({ name: 'css' }, (name, age) => {
// 异步
setTimeout(() => {
console.log('css', name, age);
}, 2000);
});
hook.callAsync('naonao', 2, err => {
console.log('end', err);
});
复制代码
打印结果
js naonao 2
end js error
复制代码
只输出了 js naonao 2 ,第二个订阅的异步函数并没有执行,而且 callback 函数的参数 js error’ 传递给了回调函数。
打印 hook.callAsync
打印一下此时的 hook.callAsync 方法:
function anonymous(name, age, _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(name, age, (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(name, age, (function (_err1) {
if (_err1) {
if (_counter > 0) {
_callback(_err1);
_counter = 0;
}
} else {
if (--_counter === 0) _done();
}
}));
} while (false);
}
复制代码
它内部是顺序执行订阅函数,但是执行订阅函数时,会给每个订阅函数添加一个回调参数,所以我们再订阅函数中有了 callback。如果我们执行 callback时传递了参数,内部就会立刻把计数器设置为 0 ,停止执行后面的订阅函数。
tapPromise 订阅
示例代码
const {
AsyncParallelHook
} = require('tapable');
const hook = new AsyncParallelHook(['name', 'age']);
hook.tapPromise('js', (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('js', name, age);
resolve(1);
}, 1000);
});
});
hook.tapPromise({ name: 'css' }, (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('css', name, age);
resolve(2);
}, 1000);
});
});
hook.promise('naonao', 2).then(result => {
console.log('end', result);
});
复制代码
打印结果
js naonao 2
css naonao 2
end undefined
复制代码
打印结果看,订阅函数中调用 resovle 并没有把值传递下去,因为 result 是 undefined。
注意
- 使用 tapPromise 订阅,必须返回一个 Promise
打印 hook.promise
打印此时的 hook.promise 方法:
function anonymous(name, age) {
"use strict";
var _context;
var _x = this._x;
return new Promise((function (_resolve, _reject) {
var _sync = true;
function _error(_err) {
if (_sync)
_resolve(Promise.resolve().then((function () { throw _err; })));
else
_reject(_err);
};
do {
var _counter = 2;
var _done = (function () {
_resolve();
});
if (_counter <= 0) break;
var _fn0 = _x[0];
var _hasResult0 = false;
var _promise0 = _fn0(name, age);
if (!_promise0 || !_promise0.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
_promise0.then((function (_result0) {
_hasResult0 = true;
if (--_counter === 0) _done();
}), function (_err0) {
if (_hasResult0) throw _err0;
if (_counter > 0) {
_error(_err0);
_counter = 0;
}
});
if (_counter <= 0) break;
var _fn1 = _x[1];
var _hasResult1 = false;
var _promise1 = _fn1(name, age);
if (!_promise1 || !_promise1.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
_promise1.then((function (_result1) {
_hasResult1 = true;
if (--_counter === 0) _done();
}), function (_err1) {
if (_hasResult1) throw _err1;
if (_counter > 0) {
_error(_err1);
_counter = 0;
}
});
} while (false);
_sync = false;
}));
}
复制代码
分析一下,它内部返回了一个 Promise。
在调用订阅函数后,它内判断返回的结果是否为 Promise,否则将抛出错误。
订阅函数执行 resolve 的结果,并没有被使用,所以在最后的结果中 result 是 undefined。
AsyncParallelBailHook
这是一个异步并行,且订阅函数返回值不是 undefined 时会中断的 Hook。
可以使用 tap、tapAsync 和 tapPromise 方法进行订阅
tap 订阅
示例代码
const {
AsyncParallelBailHook
} = require('tapable');
const hook = new AsyncParallelBailHook(['name', 'age']);
hook.tap('js', (name, age) => {
// 异步执行
setTimeout(() => {
console.log('js', name, age);
}, 1000);
});
hook.tap({ name: 'css' }, (name, age) => {
// 异步执行
setTimeout(() => {
console.log('css', name, age);
}, 2000);
return 'wrong';
});
hook.tap('node', (name, age) => {
// 同步执行
console.log('node', name, age);
});
hook.callAsync('naonao', 2, err => {
console.log('end', err);
});
复制代码
打印结果
end null
js naonao 2
css naonao 2
复制代码
从执行结果看,最后一个订阅函数 node 并没有打印出来。
所以如果中间某个订阅函数的返回值不是 undefined ,后面的订阅函数就不会继续执行了。
打印 hook.callAsync
打印此时的 hook.callAsync 方法:
function anonymous(name, age, _callback) {
"use strict";
var _context;
var _x = this._x;
var _results = new Array(3);
var _checkDone = function () {
for (var i = 0; i < _results.length; i++) {
var item = _results[i];
if (item === undefined) return false;
if (item.result !== undefined) {
_callback(null, item.result);
return true;
}
if (item.error) {
_callback(item.error);
return true;
}
}
return false;
}
do {
var _counter = 3;
var _done = (function () {
_callback();
});
if (_counter <= 0) break;
var _fn0 = _x[0];
var _hasError0 = false;
try {
var _result0 = _fn0(name, age);
} catch (_err) {
_hasError0 = true;
if (_counter > 0) {
if (0 < _results.length && ((_results.length = 1), (_results[0] = { error: _err }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}
if (!_hasError0) {
if (_counter > 0) {
if (0 < _results.length && (_result0 !== undefined && (_results.length = 1), (_results[0] = { result: _result0 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}
if (_counter <= 0) break;
if (1 >= _results.length) {
if (--_counter === 0) _done();
} else {
var _fn1 = _x[1];
var _hasError1 = false;
try {
var _result1 = _fn1(name, age);
} catch (_err) {
_hasError1 = true;
if (_counter > 0) {
if (1 < _results.length && ((_results.length = 2), (_results[1] = { error: _err }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}
if (!_hasError1) {
if (_counter > 0) {
if (1 < _results.length && (_result1 !== undefined && (_results.length = 2), (_results[1] = { result: _result1 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}
}
if (_counter <= 0) break;
if (2 >= _results.length) {
if (--_counter === 0) _done();
} else {
var _fn2 = _x[2];
var _hasError2 = false;
try {
var _result2 = _fn2(name, age);
} catch (_err) {
_hasError2 = true;
if (_counter > 0) {
if (2 < _results.length && ((_results.length = 3), (_results[2] = { error: _err }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}
if (!_hasError2) {
if (_counter > 0) {
if (2 < _results.length && (_result2 !== undefined && (_results.length = 3), (_results[2] = { result: _result2 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}
}
} while (false);
}
复制代码
重点看一下 _checkDone 方法,它在遍历时,如果 item.result !== undefined 就会直接调用 _callback。
再向下看 do…while 循环,每个订阅函数执行时如果没有错误产生,就会把它的结果赋值给 _result 对应的数组成员,然后再调用 _checkDone 方法。
if (0 < _results.length && (_result0 !== undefined && (_results.length = 1), (_results[0] = { result: _result0 }), _checkDone())) {
_counter = 0;
}
复制代码
tapAsync 订阅
示例代码
const {
AsyncParallelBailHook
} = require('tapable');
const hook = new AsyncParallelBailHook(['name', 'age']);
hook.tapAsync('js', (name, age, callback) => {
// 异步执行
setTimeout(() => {
console.log('js', name, age);
}, 1000);
callback();
});
hook.tapAsync({ name: 'css' }, (name, age, callback) => {
// 异步执行
setTimeout(() => {
console.log('css', name, age);
}, 2000);
callback('css error');
});
hook.tapAsync('node', (name, age, callback) => {
// 同步执行
console.log('node', name, age);
callback();
});
hook.callAsync('naonao', 2, err => {
console.log('end', err);
});
复制代码
打印结果
end css error
js naonao 2
css naonao 2
复制代码
从打印结果看,最后一个订阅函数 node 并没有打印出来。
注意
- 如果 订阅函数 callback 有参数,即终止后面的订阅函数执行
- 每个订阅函数都会多一个回调函数的参数,且必须执行
打印 hook.callAsync
打印此时的 hook.callAsync 方法:
function anonymous(name, age, _callback) {
"use strict";
var _context;
var _x = this._x;
var _results = new Array(3);
var _checkDone = function () {
for (var i = 0; i < _results.length; i++) {
var item = _results[i];
if (item === undefined) return false;
if (item.result !== undefined) {
_callback(null, item.result);
return true;
}
if (item.error) {
_callback(item.error);
return true;
}
}
return false;
}
do {
var _counter = 3;
var _done = (function () {
_callback();
});
if (_counter <= 0) break;
var _fn0 = _x[0];
_fn0(name, age, (function (_err0, _result0) {
if (_err0) {
if (_counter > 0) {
if (0 < _results.length && ((_results.length = 1), (_results[0] = { error: _err0 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
} else {
if (_counter > 0) {
if (0 < _results.length && (_result0 !== undefined && (_results.length = 1), (_results[0] = { result: _result0 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}
}));
if (_counter <= 0) break;
if (1 >= _results.length) {
if (--_counter === 0) _done();
} else {
var _fn1 = _x[1];
_fn1(name, age, (function (_err1, _result1) {
if (_err1) {
if (_counter > 0) {
if (1 < _results.length && ((_results.length = 2), (_results[1] = { error: _err1 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
} else {
if (_counter > 0) {
if (1 < _results.length && (_result1 !== undefined && (_results.length = 2), (_results[1] = { result: _result1 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}
}));
}
if (_counter <= 0) break;
if (2 >= _results.length) {
if (--_counter === 0) _done();
} else {
var _fn2 = _x[2];
_fn2(name, age, (function (_err2, _result2) {
if (_err2) {
if (_counter > 0) {
if (2 < _results.length && ((_results.length = 3), (_results[2] = { error: _err2 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
} else {
if (_counter > 0) {
if (2 < _results.length && (_result2 !== undefined && (_results.length = 3), (_results[2] = { result: _result2 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}
}));
}
} while (false);
}
复制代码
因为每个订阅函数都会添加一个 callback 参数,而执行结果是在这个 callback 中做的处理,所以每个订阅函数都必须执行 callback ,否则 _checkDone 和 callAsync 的 callback 就可能不会被执行。
tapPromise 订阅
示例代码
const {
AsyncParallelBailHook
} = require('tapable');
const hook = new AsyncParallelBailHook(['name', 'age']);
hook.tapPromise('js', (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('js', name, age);
resolve(1);
}, 2000);
});
});
hook.tapPromise({ name: 'css' }, (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('css', name, age);
resolve(2);
}, 1000);
});
});
hook.tapPromise('node', (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name, age);
resolve(3);
}, 3000);
});
});
hook.promise('naonao', 2).then(result => {
console.log('end', result);
}, err => {
console.log('end', erro);
});
复制代码
打印结果
css naonao 2
js naonao 2
end 1
node naonao 2
复制代码
最后 end 打印的 1, 并不是 3。而且 end 后继续打印 node nanonao 2。
注意
- 只要有一个订阅函数 resolve 或 reject 就会立刻调用 hook.promise 的 then 方法
- hook.promise 的 then 的 resolve 值是按订阅函数顺序取拥有第一个的 resolve 值。
打印 hook.promise
打印此时的 hook.promise 方法:
function anonymous(name, age) {
"use strict";
var _context;
var _x = this._x;
return new Promise((function (_resolve, _reject) {
var _sync = true;
function _error(_err) {
if (_sync)
_resolve(Promise.resolve().then((function () { throw _err; })));
else
_reject(_err);
};
var _results = new Array(3);
var _checkDone = function () {
for (var i = 0; i < _results.length; i++) {
var item = _results[i];
if (item === undefined) return false;
if (item.result !== undefined) {
_resolve(item.result);
return true;
}
if (item.error) {
_error(item.error);
return true;
}
}
return false;
}
do {
var _counter = 3;
var _done = (function () {
_resolve();
});
if (_counter <= 0) break;
var _fn0 = _x[0];
var _hasResult0 = false;
var _promise0 = _fn0(name, age);
if (!_promise0 || !_promise0.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
_promise0.then((function (_result0) {
_hasResult0 = true;
if (_counter > 0) {
if (0 < _results.length && (_result0 !== undefined && (_results.length = 1), (_results[0] = { result: _result0 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}), function (_err0) {
if (_hasResult0) throw _err0;
if (_counter > 0) {
if (0 < _results.length && ((_results.length = 1), (_results[0] = { error: _err0 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
});
if (_counter <= 0) break;
if (1 >= _results.length) {
if (--_counter === 0) _done();
} else {
var _fn1 = _x[1];
var _hasResult1 = false;
var _promise1 = _fn1(name, age);
if (!_promise1 || !_promise1.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
_promise1.then((function (_result1) {
_hasResult1 = true;
if (_counter > 0) {
if (1 < _results.length && (_result1 !== undefined && (_results.length = 2), (_results[1] = { result: _result1 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}), function (_err1) {
if (_hasResult1) throw _err1;
if (_counter > 0) {
if (1 < _results.length && ((_results.length = 2), (_results[1] = { error: _err1 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
});
}
if (_counter <= 0) break;
if (2 >= _results.length) {
if (--_counter === 0) _done();
} else {
var _fn2 = _x[2];
var _hasResult2 = false;
var _promise2 = _fn2(name, age);
if (!_promise2 || !_promise2.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise2 + ')');
_promise2.then((function (_result2) {
_hasResult2 = true;
if (_counter > 0) {
if (2 < _results.length && (_result2 !== undefined && (_results.length = 3), (_results[2] = { result: _result2 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}), function (_err2) {
if (_hasResult2) throw _err2;
if (_counter > 0) {
if (2 < _results.length && ((_results.length = 3), (_results[2] = { error: _err2 }), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
});
}
} while (false);
_sync = false;
}));
}
复制代码
重点看 _checkDone 方法,如果订阅函数的 resolve 不是 undefined 就会离开调用 _resolve。
再看 do…while ,每个订阅函数的 resolve 会存储到对应索引的 _result 值里,如果值不是 undefined 就会执行 _checkDone 方法。
AsyncSeriesHook
异步串行 Hook 。
可以使用 tap、tapAsync 和 tapPromise 方法进行订阅
tap 订阅
示例代码
const {
AsyncSeriesHook
} = require('tapable');
const hook = new AsyncSeriesHook(['name', 'age']);
hook.tap('js', (name, age) => {
setTimeout(() => {
console.log('js', name, age);
}, 2000);
});
hook.tap({ name: 'css' }, (name, age) => {
setTimeout(() => {
console.log('css', name, age);
}, 1000);
});
hook.tap('node', (name, age) => {
setTimeout(() => {
console.log('node', name, age);
}, 3000);
});
hook.callAsync('naonao', 2, result => {
console.log('end', result);
});
复制代码
打印结果:
end undefined
css naonao 2
js naonao 2
node naonao 2
复制代码
从打印结果看,串行顺序执行订阅函数。
注意
- 订阅函数是串行顺序执行,如果遇到错误就会中断执行后面的订阅函数
打印 hook.callAsync
打印此时的 hook.callAsync 方法:
function anonymous(name, age, _callback) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _hasError0 = false;
try {
_fn0(name, age);
} catch (_err) {
_hasError0 = true;
_callback(_err);
}
if (!_hasError0) {
var _fn1 = _x[1];
var _hasError1 = false;
try {
_fn1(name, age);
} catch (_err) {
_hasError1 = true;
_callback(_err);
}
if (!_hasError1) {
var _fn2 = _x[2];
var _hasError2 = false;
try {
_fn2(name, age);
} catch (_err) {
_hasError2 = true;
_callback(_err);
}
if (!_hasError2) {
_callback();
}
}
}
}
复制代码
可以看出,它是按照订阅函数的顺序执行的,如果执行过程遇到错误就会立刻调用回调函数。
tapAsync 订阅
示例代码
const {
AsyncSeriesHook
} = require('tapable');
const hook = new AsyncSeriesHook(['name', 'age']);
hook.tapAsync('js', (name, age, callback) => {
setTimeout(() => {
console.log('js', name, age);
}, 2000);
callback();
});
hook.tapAsync({ name: 'css' }, (name, age, callback) => {
setTimeout(() => {
console.log('css', name, age);
}, 1000);
callback('css error');
});
hook.tapAsync('node', (name, age, callback) => {
setTimeout(() => {
console.log('node', name, age);
}, 3000);
callback();
});
hook.callAsync('naonao', 2, result => {
console.log('end', result);
});
复制代码
打印结果
end css error
css naonao 2
js naonao 2
复制代码
从打印结果看,第三个订阅函数 node 并没有被打印出来。但是 js 后于 css 打印。
注意
- 如果订阅函数的 callback 传递了非 undefined 值,后面的订阅函数不会被执行
- 订阅函数是按照顺序执行,但是如果有异步代码,仍然按照事件环的方式执行
打印 hook.callAsync
打印此时的 hook.callAsync 方法:
function anonymous(name, age, _callback) {
"use strict";
var _context;
var _x = this._x;
function _next1() {
var _fn2 = _x[2];
_fn2(name, age, (function (_err2) {
if (_err2) {
_callback(_err2);
} else {
_callback();
}
}));
}
function _next0() {
var _fn1 = _x[1];
_fn1(name, age, (function (_err1) {
if (_err1) {
_callback(_err1);
} else {
_next1();
}
}));
}
var _fn0 = _x[0];
_fn0(name, age, (function (_err0) {
if (_err0) {
_callback(_err0);
} else {
_next0();
}
}));
}
复制代码
重点看一下每一个订阅函数执行时会添加一个回调的参数,回调参数里会调用下一个订阅函数;如果订阅函数执行错误,就会离开执行最终的回调函数,而不会执行下一个订阅函数,这样达到顺序串行的效果。
tapPromise 订阅
示例代码
const {
AsyncSeriesHook
} = require('tapable');
const hook = new AsyncSeriesHook(['name', 'age']);
hook.tapPromise('js', (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('js', name, age);
resolve();
}, 2000);
});
});
hook.tapPromise({ name: 'css' }, (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('css', name, age);
resolve(2);
}, 1000);
});
});
hook.tapPromise('node', (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name, age);
resolve(3);
}, 3000);
});
});
hook.promise('naonao', 2).then(result => {
console.log('end', result);
}, err => {
console.log('end', erro);
});
复制代码
打印结果
js naonao 2
css naonao 2
node naonao 2
end undefined
复制代码
从打印结果看,它是顺序执行的,而且订阅函数的 resolve 值并没有传递给最终的回调函数。
注意
- 执行是按照订阅的顺序执行
打印 hook.promise
打印此时的 hook.promise 方法:
function anonymous(name, age) {
"use strict";
var _context;
var _x = this._x;
return new Promise((function (_resolve, _reject) {
var _sync = true;
function _error(_err) {
if (_sync)
_resolve(Promise.resolve().then((function () { throw _err; })));
else
_reject(_err);
};
function _next1() {
var _fn2 = _x[2];
var _hasResult2 = false;
var _promise2 = _fn2(name, age);
if (!_promise2 || !_promise2.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise2 + ')');
_promise2.then((function (_result2) {
_hasResult2 = true;
_resolve();
}), function (_err2) {
if (_hasResult2) throw _err2;
_error(_err2);
});
}
function _next0() {
var _fn1 = _x[1];
var _hasResult1 = false;
var _promise1 = _fn1(name, age);
if (!_promise1 || !_promise1.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
_promise1.then((function (_result1) {
_hasResult1 = true;
_next1();
}), function (_err1) {
if (_hasResult1) throw _err1;
_error(_err1);
});
}
var _fn0 = _x[0];
var _hasResult0 = false;
var _promise0 = _fn0(name, age);
if (!_promise0 || !_promise0.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
_promise0.then((function (_result0) {
_hasResult0 = true;
_next0();
}), function (_err0) {
if (_hasResult0) throw _err0;
_error(_err0);
});
_sync = false;
}));
}
复制代码
重点看一下每一个订阅函数执行的 then 方法,
_promise0.then 方法内部调用 _next0,而它内部是调用第二个订阅函数 _promise1,_promise1.then 内部接着调用 _next1,这样达到穿行的执行效果。
AsyncSeriesBailHook
异步串行 Hook,但是只要订阅函数的返回值不是 undefined 就会中断执行后面的订阅函数。
可以使用 tap、tapAsync 和 tapPromise 方法进行订阅
tap 订阅
示例代码
const {
AsyncSeriesBailHook
} = require('tapable');
const hook = new AsyncSeriesBailHook(['name', 'age']);
hook.tap('js', (name, age) => {
setTimeout(() => {
console.log('js', name, age);
}, 2000);
});
hook.tap({ name: 'css' }, (name, age) => {
setTimeout(() => {
console.log('css', name, age);
}, 1000);
return 'wrong';
});
hook.tap('node', (name, age) => {
setTimeout(() => {
console.log('node', name, age);
}, 3000);
});
hook.callAsync('naonao', 2, result => {
console.log('end', result);
});
复制代码
打印结果
end null
css naonao 2
js naonao 2
复制代码
从打印结果看,最后一个订阅函数 node 并没有被打印,因为第二个订阅函数返回了一个非 undefined 的值。
打印 hook.callAsync
打印此时的 hook.callAsync 方法:
function anonymous(name, age, _callback) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _hasError0 = false;
try {
var _result0 = _fn0(name, age);
} catch (_err) {
_hasError0 = true;
_callback(_err);
}
if (!_hasError0) {
if (_result0 !== undefined) {
_callback(null, _result0);
} else {
var _fn1 = _x[1];
var _hasError1 = false;
try {
var _result1 = _fn1(name, age);
} catch (_err) {
_hasError1 = true;
_callback(_err);
}
if (!_hasError1) {
if (_result1 !== undefined) {
_callback(null, _result1);
} else {
var _fn2 = _x[2];
var _hasError2 = false;
try {
var _result2 = _fn2(name, age);
} catch (_err) {
_hasError2 = true;
_callback(_err);
}
if (!_hasError2) {
if (_result2 !== undefined) {
_callback(null, _result2);
} else {
_callback();
}
}
}
}
}
}
}
复制代码
从代码可以看出,它是按顺序执行,如果订阅函数的返回值不是 undefined 或者 有错误,都会立刻执行最终的回调。
tapAsync 订阅
示例代码
const {
AsyncSeriesBailHook
} = require('tapable');
const hook = new AsyncSeriesBailHook(['name', 'age']);
hook.tapAsync('js', (name, age, callback) => {
setTimeout(() => {
console.log('js', name, age);
}, 2000);
callback();
});
hook.tapAsync({ name: 'css' }, (name, age, callback) => {
setTimeout(() => {
console.log('css', name, age);
}, 1000);
callback('css error');
});
hook.tapAsync('node', (name, age, callback) => {
setTimeout(() => {
console.log('node', name, age);
}, 3000);
callback();
});
hook.callAsync('naonao', 2, result => {
console.log('end', result);
});
复制代码
打印结果
end css error
css naonao 2
js naonao 2
复制代码
从打印结果看,最后一个订阅函数 node 并没有被打印。
之所以先打印出 css,在打印 js,那是因为内部 setTimeout 引起的。
打印 hook.callAsync
打印此时的 hook.callAsync 方法:
function anonymous(name, age, _callback) {
"use strict";
var _context;
var _x = this._x;
function _next1() {
var _fn2 = _x[2];
_fn2(name, age, (function (_err2, _result2) {
if (_err2) {
_callback(_err2);
} else {
if (_result2 !== undefined) {
_callback(null, _result2);
} else {
_callback();
}
}
}));
}
function _next0() {
var _fn1 = _x[1];
_fn1(name, age, (function (_err1, _result1) {
if (_err1) {
_callback(_err1);
} else {
if (_result1 !== undefined) {
_callback(null, _result1);
} else {
_next1();
}
}
}));
}
var _fn0 = _x[0];
_fn0(name, age, (function (_err0, _result0) {
if (_err0) {
_callback(_err0);
} else {
if (_result0 !== undefined) {
_callback(null, _result0);
} else {
_next0();
}
}
}));
}
复制代码
从代码中可以看出,每个订阅函数都会添加一个 callback 的参数,它内部执行,如果有错误或返回值不是 undefined 都会立刻执行最终的回调,否则才会执行下一个订阅函数。
这样就达到了顺序执行的效果。
tapPromise 订阅
示例代码
const {
AsyncSeriesBailHook
} = require('tapable');
const hook = new AsyncSeriesBailHook(['name', 'age']);
hook.tapPromise('js', (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('js', name, age);
resolve();
}, 2000);
});
});
hook.tapPromise({ name: 'css' }, (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('css', name, age);
resolve(2);
}, 1000);
});
});
hook.tapPromise('node', (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name, age);
resolve(3);
}, 3000);
});
});
hook.promise('naonao', 2).then(result => {
console.log('end', result);
}, err => {
console.log('end', err);
});
复制代码
打印结果
js naonao 2
css naonao 2
end 2
复制代码
从打印结果看,最后一个订阅函数 node 并没有被打印。
执行是按照顺序串行执行的,当遇到订阅函数的 resolve 传递的不是 undefined 值时,就会执行最终的 promise.then,后面的订阅函数就不会再执行了。
打印 hook.promise
打印此时的 hook.promise 方法:
function anonymous(name, age) {
"use strict";
var _context;
var _x = this._x;
return new Promise((function (_resolve, _reject) {
var _sync = true;
function _error(_err) {
if (_sync)
_resolve(Promise.resolve().then((function () { throw _err; })));
else
_reject(_err);
};
function _next1() {
var _fn2 = _x[2];
var _hasResult2 = false;
var _promise2 = _fn2(name, age);
if (!_promise2 || !_promise2.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise2 + ')');
_promise2.then((function (_result2) {
_hasResult2 = true;
if (_result2 !== undefined) {
_resolve(_result2);
} else {
_resolve();
}
}), function (_err2) {
if (_hasResult2) throw _err2;
_error(_err2);
});
}
function _next0() {
var _fn1 = _x[1];
var _hasResult1 = false;
var _promise1 = _fn1(name, age);
if (!_promise1 || !_promise1.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
_promise1.then((function (_result1) {
_hasResult1 = true;
if (_result1 !== undefined) {
_resolve(_result1);
} else {
_next1();
}
}), function (_err1) {
if (_hasResult1) throw _err1;
_error(_err1);
});
}
var _fn0 = _x[0];
var _hasResult0 = false;
var _promise0 = _fn0(name, age);
if (!_promise0 || !_promise0.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
_promise0.then((function (_result0) {
_hasResult0 = true;
if (_result0 !== undefined) {
_resolve(_result0);
} else {
_next0();
}
}), function (_err0) {
if (_hasResult0) throw _err0;
_error(_err0);
});
_sync = false;
}));
}
复制代码
从代码中能看出,订阅函数的 then 方法,如果 resolve 的值不是 undefined 就会调用最终的 _resolve 方法,并把值传递下去,否则调用下一个订阅函数。
AsyncSeriesLoopHook
异步串行循环 Hook
可以使用 tap、tapAsync 和 tapPromise 方法进行订阅
tap 订阅
示例代码
const {
AsyncSeriesLoopHook
} = require('tapable');
const hook = new AsyncSeriesLoopHook(['name', 'age']);
let count1 = 0;
let count2 = 0;
hook.tap('js', (name, age) => {
if (++count1 >= 2) {
console.log('js', name, age, count1);
return;
} else {
console.log('js', name, age, count1);
return 'js1';
}
});
hook.tap('node', (name, age) => {
if (++count2 >= 2) {
console.log('node', name, age, count2);
return;
} else {
console.log('node', name, age, count2);
return 'node2';
}
});
hook.callAsync('naonao', 2, err => {
console.log('end', err);
});
复制代码
打印结果
js naonao 2 1
js naonao 2 2
node naonao 2 1
js naonao 2 3
node naonao 2 2
end undefined
复制代码
从打印结果看,订阅函数返回值不是 undefined 时,会从第一个订阅开始循环执行。
打印 hook.callAsync
打印此时的 hook.callAsync 方法:
function anonymous(name, age, _callback) {
"use strict";
var _context;
var _x = this._x;
var _loop;
do {
_loop = false;
var _fn0 = _x[0];
var _hasError0 = false;
try {
var _result0 = _fn0(name, age);
} catch (_err) {
_hasError0 = true;
_callback(_err);
}
if (!_hasError0) {
if (_result0 !== undefined) {
_loop = true;
} else {
var _fn1 = _x[1];
var _hasError1 = false;
try {
var _result1 = _fn1(name, age);
} catch (_err) {
_hasError1 = true;
_callback(_err);
}
if (!_hasError1) {
if (_result1 !== undefined) {
_loop = true;
} else {
if (!_loop) {
_callback();
}
}
}
}
}
} while (_loop);
}
复制代码
从代码中能看出,每个订阅函数的返回值如果不是 undefined 就会设置 _loop 为 true ,则 do…while 就会从头开始循环。
tapAsync 订阅
示例代码
const {
AsyncSeriesLoopHook
} = require('tapable');
const hook = new AsyncSeriesLoopHook(['name', 'age']);
let count1 = 0;
let count2 = 0;
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);
});
复制代码
打印结果
js naonao 2 1
js naonao 2 2
node naonao 2 1
js naonao 2 3
node naonao 2 2
end undefined
复制代码
从打印结果看,如果 callback 第二个参数不是 undefined 就会从头开始循环。
打印 hook.callAsync
打印此时的 hook.callAsync 方法:
function anonymous(name, age, _callback) {
"use strict";
var _context;
var _x = this._x;
var _looper = (function () {
var _loopAsync = false;
var _loop;
do {
_loop = false;
function _next0() {
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 _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();
}
复制代码
从代码中能看出,定义了一个 _looper 方法,每个订阅函数都会有一个回调函数作为参数,如果这个回调函数第二个参数不是 undefined,就会再次调用 _looper 方法,这样就达到了从定一个订阅函数循环执行的效果。
tapPromise 订阅
示例代码
const {
AsyncSeriesLoopHook
} = require('tapable');
const hook = new AsyncSeriesLoopHook(['name', 'age']);
let count1 = 0;
let count2 = 0;
hook.tapPromise('js', (name, age) => {
return new Promise((resolve, reject) => {
if (++count1 >= 2) {
console.log('js', name, age, count1);
resolve();
} else {
console.log('js', name, age, count1);
resolve('js1');
}
});
});
hook.tapPromise('node', (name, age) => {
return new Promise((resolve, reject) => {
if (++count2 >= 2) {
console.log('node', name, age, count1);
resolve();
} else {
console.log('node', name, age, count1);
resolve('node2');
}
});
});
hook.promise('naonao', 2).then(result => {
console.log('end', result);
}, err => {
console.log('end', err);
});
复制代码
打印结果
js naonao 2 1
js naonao 2 2
node naonao 2 2
js naonao 2 3
node naonao 2 3
end undefined
复制代码
从打印结果看,订阅函数的 resolve 不是 undefined 就会从头循环执行,直到遇到 undefined 才会执行下一个订阅函数。
打印 hook.promise
打印此时的 hook.promise 方法:
function anonymous(name, age) {
"use strict";
var _context;
var _x = this._x;
return new Promise((function (_resolve, _reject) {
var _sync = true;
function _error(_err) {
if (_sync)
_resolve(Promise.resolve().then((function () { throw _err; })));
else
_reject(_err);
};
var _looper = (function () {
var _loopAsync = false;
var _loop;
do {
_loop = false;
function _next0() {
var _fn1 = _x[1];
var _hasResult1 = false;
var _promise1 = _fn1(name, age);
if (!_promise1 || !_promise1.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
_promise1.then((function (_result1) {
_hasResult1 = true;
if (_result1 !== undefined) {
_loop = true;
if (_loopAsync) _looper();
} else {
if (!_loop) {
_resolve();
}
}
}), function (_err1) {
if (_hasResult1) throw _err1;
_error(_err1);
});
}
var _fn0 = _x[0];
var _hasResult0 = false;
var _promise0 = _fn0(name, age);
if (!_promise0 || !_promise0.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
_promise0.then((function (_result0) {
_hasResult0 = true;
if (_result0 !== undefined) {
_loop = true;
if (_loopAsync) _looper();
} else {
_next0();
}
}), function (_err0) {
if (_hasResult0) throw _err0;
_error(_err0);
});
} while (_loop);
_loopAsync = true;
});
_looper();
_sync = false;
}));
}
复制代码
从代码中可以看出,它定义了一个 _looper 函数,当订阅函数的 resolve 不是 undefined 就会重新执行这个 _looper 函数,也就从第一个订阅函数开始执行了。
AsyncSeriesWaterfallHook
异步串行 Hook,但是会把当前订阅函数的返回值(非undefined)传递给下一个订阅函数,并当作定一个参数使用。
可以使用 tap、tapAsync 和 tapPromise 方法进行订阅
tap 订阅
示例代码
const {
AsyncSeriesWaterfallHook
} = require('tapable');
const hook = new AsyncSeriesWaterfallHook(['name', 'age']);
hook.tap('js', (name, age) => {
setTimeout(() => {
console.log('js', name, age);
}, 2000);
return 'js1';
});
hook.tap({ name: 'css' }, (name, age) => {
setTimeout(() => {
console.log('css', name, age);
}, 1000);
return 'css2';
});
hook.tap('node', (name, age) => {
setTimeout(() => {
console.log('node', name, age);
}, 3000);
return 'node3';
});
hook.callAsync('naonao', 2, (err, result) => {
console.log('end', err, result);
});
复制代码
打印结果
end null node3
css js1 2
js naonao 2
node css2 2
复制代码
从打印结果看出,上一个订阅函数的返回值(非 undefined)传递到了当前订阅函数中,并且作为第一个参数使用。
打印 hook.callAsync
打印此时的 hook.callAsync 方法:
function anonymous(name, age, _callback) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
var _hasError0 = false;
try {
var _result0 = _fn0(name, age);
} catch (_err) {
_hasError0 = true;
_callback(_err);
}
if (!_hasError0) {
if (_result0 !== undefined) {
name = _result0;
}
var _fn1 = _x[1];
var _hasError1 = false;
try {
var _result1 = _fn1(name, age);
} catch (_err) {
_hasError1 = true;
_callback(_err);
}
if (!_hasError1) {
if (_result1 !== undefined) {
name = _result1;
}
var _fn2 = _x[2];
var _hasError2 = false;
try {
var _result2 = _fn2(name, age);
} catch (_err) {
_hasError2 = true;
_callback(_err);
}
if (!_hasError2) {
if (_result2 !== undefined) {
name = _result2;
}
_callback(null, name);
}
}
}
}
复制代码
从代码中可以看出,每个订阅函数的返回值,如果不是 undefined 就会赋值给第一个参数,然后再继续调用下一个订阅函数。
在看一下 _callback 方法的调用,它的第二个参数就是 callAsync 调用时传递的第一个参数。
tapAsync 订阅
示例代码
const {
AsyncSeriesWaterfallHook
} = require('tapable');
const hook = new AsyncSeriesWaterfallHook(['name', 'age']);
hook.tapAsync('js', (name, age, callback) => {
setTimeout(() => {
console.log('js', name, age);
}, 2000);
callback(null, 'js1');
});
hook.tapAsync({ name: 'css' }, (name, age, callback) => {
setTimeout(() => {
console.log('css', name, age);
}, 1000);
callback(null, 'css2');
});
hook.tapAsync('node', (name, age, callback) => {
setTimeout(() => {
console.log('node', name, age);
}, 3000);
callback(null, 'node3');
});
hook.callAsync('naonao', 2, (err, result) => {
console.log('end', err, result);
});
复制代码
打印结果
end null node3
css js1 2
js naonao 2
node css2 2
复制代码
从打印结果看,订阅函数中 callback 函数的第二个参数会传递下去。
打印 hook.callAsync
打印此时的 hook.callAsync 方法:
function anonymous(name, age, _callback) {
"use strict";
var _context;
var _x = this._x;
function _next1() {
var _fn2 = _x[2];
_fn2(name, age, (function (_err2, _result2) {
if (_err2) {
_callback(_err2);
} else {
if (_result2 !== undefined) {
name = _result2;
}
_callback(null, name);
}
}));
}
function _next0() {
var _fn1 = _x[1];
_fn1(name, age, (function (_err1, _result1) {
if (_err1) {
_callback(_err1);
} else {
if (_result1 !== undefined) {
name = _result1;
}
_next1();
}
}));
}
var _fn0 = _x[0];
_fn0(name, age, (function (_err0, _result0) {
if (_err0) {
_callback(_err0);
} else {
if (_result0 !== undefined) {
name = _result0;
}
_next0();
}
}));
}
复制代码
从代码中可以看出,订阅函数第三个参数是一个方法,会把方法的第二个参数值(非 undefined) 赋值给 name。然后再调用下一个订阅函数。
tapPromise 订阅
示例代码
const {
AsyncSeriesWaterfallHook
} = require('tapable');
const hook = new AsyncSeriesWaterfallHook(['name', 'age']);
hook.tapPromise('js', (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('js', name, age);
resolve('js1');
}, 2000);
});
});
hook.tapPromise({ name: 'css' }, (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('css', name, age);
resolve('css2');
}, 1000);
});
});
hook.tapPromise('node', (name, age) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('node', name, age);
resolve('node3');
}, 3000);
});
});
hook.promise('naonao', 2).then(result => {
console.log('end', result);
}, err => {
console.log('end', err);
});
复制代码
打印结果
js naonao 2
css js1 2
node css2 2
end node3
复制代码
从打印结果看,订阅函数的 resolve 值会传递给下一个订阅函数。
打印 hook.promise
打印此时的 hook.promise 方法:
function anonymous(name, age) {
"use strict";
var _context;
var _x = this._x;
return new Promise((function (_resolve, _reject) {
var _sync = true;
function _error(_err) {
if (_sync)
_resolve(Promise.resolve().then((function () { throw _err; })));
else
_reject(_err);
};
function _next1() {
var _fn2 = _x[2];
var _hasResult2 = false;
var _promise2 = _fn2(name, age);
if (!_promise2 || !_promise2.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise2 + ')');
_promise2.then((function (_result2) {
_hasResult2 = true;
if (_result2 !== undefined) {
name = _result2;
}
_resolve(name);
}), function (_err2) {
if (_hasResult2) throw _err2;
_error(_err2);
});
}
function _next0() {
var _fn1 = _x[1];
var _hasResult1 = false;
var _promise1 = _fn1(name, age);
if (!_promise1 || !_promise1.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
_promise1.then((function (_result1) {
_hasResult1 = true;
if (_result1 !== undefined) {
name = _result1;
}
_next1();
}), function (_err1) {
if (_hasResult1) throw _err1;
_error(_err1);
});
}
var _fn0 = _x[0];
var _hasResult0 = false;
var _promise0 = _fn0(name, age);
if (!_promise0 || !_promise0.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
_promise0.then((function (_result0) {
_hasResult0 = true;
if (_result0 !== undefined) {
name = _result0;
}
_next0();
}), function (_err0) {
if (_hasResult0) throw _err0;
_error(_err0);
});
_sync = false;
}));
}
复制代码
从代码中可以看出,订阅函数的 resolve 值如果不是 undefined 会赋值给 name,然后再调用下一个订阅函数。
结语
本篇是 Tapable 所有的 Hook 的应用示例。
通过示例可以很好的了解各个 Hook 的特性和用法。同时打印出各个 Hook 的调用方法,能帮助我们更好的了解,它们内部的真正执行逻辑。
下一篇是有关 Interception 拦截器的内容。
更多精彩,请关注微信公众号:闹闹前端