webpack 事件处理机制Tapable
webpack 的诸多核心模块都是tapable的子类, tapable提供了一套完整事件订阅和发布的机制,让webpack的执行的流程交给了订阅的插件去处理, 当然这套机制为开发者订阅流程事件去定制自己构建模式和方案提供了更多的便利性, 从基础的原理角度说,tapable就是一套观察者模式,并在此基础上提供了较为丰富的订阅和发布方式,如 call/async /promise,以此支持更多的处理场景。
- 以下是tapable 提供的所有的hook类型
exports.__esModule = true;
exports.Tapable = require("./Tapable");
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.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
exports.HookMap = require("./HookMap");
exports.MultiHook = require("./MultiHook");
hook 分析
hook 主要分一下类型 async / sync
总体介绍
在正式分析源码之前,先对每一种hook的进行功能介绍和简单源码分析
| 序号 | 钩子名称 | 执行方式 | 使用要点 |
|---|---|---|---|
| 1 | SyncHook | 同步串行 | 不关心监听函数的返回值 |
| 2 | SyncBailHook | 同步串行 | 只要监听函数中有一个函数的返回值不为 null,则跳过剩下所有的逻辑 |
| 3 | SyncWaterfallHook | 同步串行 | 上一个监听函数的返回值可以传给下一个监听函数 |
| 4 | SyncLoopHook | 同步循环 | 当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环 |
| 5 | AsyncParallelHook | 异步并发 | 不关心监听函数的返回值 |
| 6 | AsyncParallelBailHook | 异步并发 | 只要监听函数的返回值不为 null,就会忽略后面的监听函数执行,直接跳跃到callAsync等触发函数绑定的回调函数,然后执行这个被绑定的回调函数 |
| 7 | AsyncSeriesHook | 异步串行 | 不关心callback()的参数 |
| 8 | AsyncSeriesBailHook | 异步串行 | callback()的参数不为null,就会直接执行callAsync等触发函数绑定的回调函数 |
| 9 | AsyncSeriesWaterfallHook | 异步串行 | 上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数 |
Demo验证
1 sync
const {
SyncHook,
SyncBailHook,
SyncLoopHook,
SyncWaterfallHook
} = require('tapable')
class SyncHookDemo{
constructor(){
this.hooks = {
sh: new SyncHook(['name', 'age']),
sbh: new SyncBailHook(['name', 'age']),
slh: new SyncLoopHook(['name']),
swh: new SyncWaterfallHook(['name', 'nickname', 'user'])
}
}
}
const hdemo = new SyncHookDemo();
- SyncHook
hdemo.hooks.sh.tap('record', (name, age)=>{
console.log(`record: ${name}, ${age}`);
return name;
});
hdemo.hooks.sh.tap('save', (name, age)=>{
console.log(`save: ${name}, ${age} `)
});
hdemo.hooks.sh.call('张三', ~~(Math.random() * 100))
// 结果
record: 张三, 75
save: 张三, 75
synchook就是很简单的订阅 同步发布 不关心订阅函数返回值, 一口气把把所有订阅者执行一遍
原理 就是简单的订阅和发布
class SyncHook{
constructor(){
this.subs = [];
}
tap(fn){
this.sub.push(fn);
}
call(...args){
this.subs.forEach(fn=>fn(...args));
}
}
- SyncBailHook
hdemo.hooks.sbh.tap('cache', (name, age)=>{
console.log(`get data from cache: ${name}, ${age}`)
return name;
})
hdemo.hooks.sbh.tap('db', (name, age)=>{
console.log(`get data from db: ${name}, ${age}`)
})
hdemo.hooks.sbh.call('李四', ~~(Math.random() * 100))
// 结果
get data from cache: 李四, 36
遇到订阅函数返回不为空的情况下 就会停止执行剩余的callback
原理
class SyncBailHook{
constructor(){
this.subs = [];
}
tap(fn){
this.sub.push(fn);
}
call(...args){
for(let i=0; i< this.subs.length; i++){
const result = this.subs[i](...args);
result && break;
}
}
- SyncWaterHook
hdemo.hooks.swh.tap('getName', (name)=>{
console.log('getName', name);
return 'OK' + name;
})
hdemo.hooks.swh.tap('getNikename', (name)=>{
console.log('preName:', name);
})
hdemo.hooks.swh.call('车库');
// 结果
getName 车库
preName: OK车库
上一个监听函数的返回值可以传给下一个监听函数
原理:
class SyncWaterHook{
constructor(){
this.subs = [];
}
tap(fn){
this.sub.push(fn);
}
call(...args){
let result = null;
for(let i = 0, l = this.hooks.length; i < l; i++) {
let hook = this.hooks[i];
result = i == 0 ? hook(...args): hook(result);
}
}
}
- SyncLoopHook
let count = 5;
hdemo.hooks.slh.tap('loop', (name) => {
console.log('count: ', count--, name);
if (count > 0) {
return true;
}
return;
})
hdemo.hooks.slh.call('测试loop');
// 结果
count: 5 测试loop
count: 4 测试loop
count: 3 测试loop
count: 2 测试loop
count: 1 测试loop
当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环
原理
class SyncLooHook{
constructor(){
this.subs = [];
}
tap(fn){
this.sub.push(fn);
}
call(...args){
let result;
do {
result = this.hook(...arguments);
} while (result!==undefined)
}
}
2. async
const {
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require('tapable')
class AsyncHookDemo{
constructor(){
this.hooks = {
aph: new AsyncParallelHook(['name']),
apbh: new AsyncParallelBailHook(['name']),
ash: new AsyncSeriesHook(['name']),
asbh: new AsyncSeriesBailHook(['name']),
aswh: new AsyncSeriesWaterfallHook(['name'])
}
}
}
const shdemo = new AsyncHookDemo();
- AsyncParallelHook 异步并行
shdemo.hooks.aph.tap('req-1', (name)=>{
console.log(name, 'req-1');
// throw new Error('0000')
// callback()
})
shdemo.hooks.aph.tap('req-2', (name)=>{
console.log(name, 'req-2');
// callback('reqcallback2')
})
shdemo.hooks.aph.tapAsync('areq-1', (name, cb)=>{
console.log('areq-1', name);
cb();
})
shdemo.hooks.aph.tapAsync('areq-2', (name, cb)=>{
console.log('areq-2', name);
cb();
})
shdemo.hooks.aph.tapAsync('areq-3', (name, cb)=>{
console.log('areq-3', name);
cb(null ,'over');
})
shdemo.hooks.aph.tapAsync('areq-4', (name, cb)=>{
console.log('areq-4', name);
cb();
})
shdemo.hooks.aph.callAsync('<---------90---->', (err, result)=>{
if(err){
return console.log('err', err);
}
console.log('jieshu', result)
});
// promise 不在验证 特别简单
<---------90----> req-1
<---------90----> req-2
areq-1 <---------90---->
areq-2 <---------90---->
areq-3 <---------90---->
areq-4 <---------90---->
jieshu undefined
结论:
- hook 不在乎callback的返回值
- callback 第一个参数给给值 表示异常 就会结束
- 监听函数throw 一个异常会被最后的callback捕获
- AsyncParallelBailHook
可能看起来有点懵, 为什么是这样,我们还是从源码入手,看看各类hook源码
然后在demo验证 分析的对否
如果想自己想试试的,可以直接使用参考的资料的第一个链接,
先从基类
Hook源代码
"use strict";
class Hook {
constructor(args) {
if (!Array.isArray(args)) args = [];
this._args = args;
this.taps = [];
this.interceptors = [];
this.call = this._call;
this.promise = this._promise;
this.callAsync = this._callAsync;
this._x = undefined;
}
compile(options) {
throw new Error("Abstract: should be overriden");
}
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
tap(options, fn) {
if (typeof options === "string") options = { name: options };
if (typeof options !== "object" || options === null)
throw new Error(
"Invalid arguments to tap(options: Object, fn: function)"
);
options = Object.assign({ type: "sync", fn: fn }, options);
if (typeof options.name !== "string" || options.name === "")
throw new Error("Missing name for tap");
options = this._runRegisterInterceptors(options);
this._insert(options);
}
tapAsync(options, fn) {
if (typeof options === "string") options = { name: options };
if (typeof options !== "object" || options === null)
throw new Error(
"Invalid arguments to tapAsync(options: Object, fn: function)"
);
options = Object.assign({ type: "async", fn: fn }, options);
if (typeof options.name !== "string" || options.name === "")
throw new Error("Missing name for tapAsync");
options = this._runRegisterInterceptors(options);
this._insert(options);
}
tapPromise(options, fn) {
if (typeof options === "string") options = { name: options };
if (typeof options !== "object" || options === null)
throw new Error(
"Invalid arguments to tapPromise(options: Object, fn: function)"
);
options = Object.assign({ type: "promise", fn: fn }, options);
if (typeof options.name !== "string" || options.name === "")
throw new Error("Missing name for tapPromise");
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;
}
withOptions(options) {
const mergeOptions = opt =>
Object.assign({}, options, typeof opt === "string" ? { name: opt } : opt);
// Prevent creating endless prototype chains
options = Object.assign({}, options, this._withOptions);
const base = this._withOptionsBase || this;
const newHook = Object.create(base);
(newHook.tapAsync = (opt, fn) => base.tapAsync(mergeOptions(opt), fn)),
(newHook.tap = (opt, fn) => base.tap(mergeOptions(opt), fn));
newHook.tapPromise = (opt, fn) => base.tapPromise(mergeOptions(opt), fn);
newHook._withOptions = options;
newHook._withOptionsBase = base;
return newHook;
}
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]);
}
}
_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;
}
}
function createCompileDelegate(name, type) {
return function lazyCompileHook(...args) {
this[name] = this._createCall(type);
return this[name](...args);
};
}
Object.defineProperties(Hook.prototype, {
_call: {
value: createCompileDelegate("call", "sync"),
configurable: true,
writable: true
},
_promise: {
value: createCompileDelegate("promise", "promise"),
configurable: true,
writable: true
},
_callAsync: {
value: createCompileDelegate("callAsync", "async"),
configurable: true,
writable: true
}
});
hook 实现的思路是: hook 使用观察者模式,构造函数需要提供一个参数数组,就是派发事件的参数集合
constructor(args) {
if (!Array.isArray(args)) args = [];
this._args = args;
this.taps = [];
this.interceptors = [];
this.call = this._call;
this.promise = this._promise;
this.callAsync = this._callAsync;
this._x = undefined;
}
- taps 订阅函数集合
- interceptors 拦截集合 配置在执行派发之前的拦截
- call 同步触发对象
- promise promise方式触发的对象
- callSync 异步触发对象
- _x 应用于生成执行函数 监听集合 后续详细介绍这个_x 的使用意义 (这个名字起得有点怪)
既然hook是观察者模式实现的,我们就顺着观察者模式的思路去逐步解析hook的实现方法
hook之订阅
- 同步订阅 tap 方法
tap(options, fn) {
//传入的参数是 字符串 整理options = { name: options}
if (typeof options === "string") options = { name: options };
// options 如果传入不是字符串和Object就扔出异常 当然从订阅角度来说 有个唯一性名称就行 O
if (typeof options !== "object" || options === null)
throw new Error(
"Invalid arguments to tap(options: Object, fn: function)"
);
// 加入类型和callback 很显然这里对我们传入的callback 没有进行任何处理
options = Object.assign({ type: "sync", fn: fn }, options);
if (typeof options.name !== "string" || options.name === "")
throw new Error("Missing name for tap");
// 如果option中存有拦截器 注入进去即可 拦截器分成 before/after类型
options = this._runRegisterInterceptors(options);
// 保存订阅
this._insert(options);
}
// 下面将详细分析 如何处理我们的订阅函数 this._insert
- _insert 方法
insert 依赖方法
_resetCompilation
// 定义方法委托 让hook[name]方法 = hook._createCall(type) 产生
// sync 调用call
// promise 调用promise
// async 调用 callAsync
function createCompileDelegate(name, type) {
return function lazyCompileHook(...args) {
// 这里很妙 将生成函数的指向上下文 给定给this 也就是说 模板代码中
// this 可以获取hook的属性和方法 this._x 就可以排上用途了 具体的在
//factory 里面分析
this[name] = this._createCall(type);
return this[name](...args);
};
}
Object.defineProperties(Hook.prototype, {
_call: {
value: createCompileDelegate("call", "sync"),
configurable: true,
writable: true
},
_promise: {
value: createCompileDelegate("promise", "promise"),
configurable: true,
writable: true
},
_callAsync: {
value: createCompileDelegate("callAsync", "async"),
configurable: true,
writable: true
}
});
// 重置 call / callAsync promise
// 为什么要重置
_resetCompilation() {
this.call = this._call;
this.callAsync = this._callAsync;
this.promise = this._promise;
}
// 根据源码可以知道 重置就是让call/callAsync/promise方法来自原型上的 _call/_promise/_callAsync 同时赋值
insert 源码
_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);
}
// 通过item 如果配置了before 和 stage 来控制item在 taps的位置
// 如果监听函数没有配置这两个参数就会执行 this.taps[i] = item
// 最后位置保存新加入的订阅 从此完成订阅
//如果配置了before 就会移动位置 根据name值 将item放在相应的位置
//stage 同理
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;
}
在上面分析到的 call/callAsync/promise 方法中 使用到了createCall 方法和 _compile
- 异步订阅tapAsync
tapAsync(options, fn) {
if (typeof options === "string") options = { name: options };
if (typeof options !== "object" || options === null)
throw new Error(
"Invalid arguments to tapAsync(options: Object, fn: function)"
);
// 只是将type改成了 async 其余同tap
options = Object.assign({ type: "async", fn: fn }, options);
if (typeof options.name !== "string" || options.name === "")
throw new Error("Missing name for tapAsync");
options = this._runRegisterInterceptors(options);
this._insert(options);
}
- 异步订阅 tapPromise
tapPromise(options, fn) {
if (typeof options === "string") options = { name: options };
if (typeof options !== "object" || options === null)
throw new Error(
"Invalid arguments to tapPromise(options: Object, fn: function)"
);
options = Object.assign({ type: "promise", fn: fn }, options);
if (typeof options.name !== "string" || options.name === "")
throw new Error("Missing name for tapPromise");
options = this._runRegisterInterceptors(options);
this._insert(options);
}
// 其实源码订阅方法有重构的空间 好多代码冗余
hook之发布
// 这个方法交给子类自己实现 也就是说 怎么发布订阅由子类自己实现
compile(options) {
throw new Error("Abstract: should be overriden");
}
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
问题 ?
this._x 没有做任何处理 ? 带着这个问题我们去分析
也就是说hook将自己的发布交给了子类去实现
HookCodeFactory
hook的发布方法(compile)交给 子类自己去实现,同时提供了代码组装工程类,这个类为所有类别的hook的提供了代码生成基础方法,下面我们详细分析这个类的代码组成
HookCodeFactory 最终生成可执行的代码片段和普通的模板编译方法差不多
class HookCodeFactory {
/**
* config 配置
options = 就是
{
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
}
*/
constructor(config) {
this.config = config;
this.options = undefined;
this._args = undefined;
}
/**
* options = {
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
}
*/
create(options) {
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`,
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":
// promise代码模板
let code = "";
code += '"use strict";\n';
code += "return new Promise((_resolve, _reject) => {\n";
code += "var _sync = true;\n";
code += this.header();
code += this.content({
onError: err => {
let code = "";
code += "if(_sync)\n";
code += `_resolve(Promise.resolve().then(() => { throw ${err}; }));\n`;
code += "else\n";
code += `_reject(${err});\n`;
return code;
},
onResult: result => `_resolve(${result});\n`,
onDone: () => "_resolve();\n"
});
code += "_sync = false;\n";
code += "});\n";
fn = new Function(this.args(), code);
break;
}
// 重置 options和args
this.deinit();
return fn;
}
setup(instance, options) {
// 安装实例 让模板代码里的this._x 给与值
// 这里可以解释 hook源码中 定义未赋值_x的疑问了
// _x 其实就是taps 监听函数的集合
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;
// 赋值args的参数
this._args = options.args.slice();
}
deinit() {
this.options = undefined;
this._args = undefined;
}
// 代码header部分
// 这里定义了 _X的值
// interceptors 的执行
header() {
let code = "";
if (this.needContext()) {
code += "var _context = {};\n";
} else {
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";
}
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;
}
needContext() {
for (const tap of this.options.taps) if (tap.context) return true;
return false;
}
// 触发订阅
/**
* 构建发布方法
* 分sync async promise
*/
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`;
}
}
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) {
code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
} else {
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`;
// 需要返回promise
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;
}
// 调用串行
callTapsSeries({ onError, onResult, onDone, rethrowIfPossible }) {
if (this.options.taps.length === 0) return onDone();
const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");
const next = i => {
if (i >= this.options.taps.length) {
return onDone();
}
const done = () => next(i + 1);
const doneBreak = skipDone => {
if (skipDone) return "";
return onDone();
};
return this.callTap(i, {
onError: error => onError(i, error, done, doneBreak),
onResult:
onResult &&
(result => {
return onResult(i, result, done, doneBreak);
}),
onDone:
!onResult &&
(() => {
return done();
}),
rethrowIfPossible:
rethrowIfPossible && (firstAsync < 0 || i < firstAsync)
});
};
return next(0);
}
// 触发循环调用
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}]`;
}
}
有点长 下一篇具体分析每一种类型的hook 暂时先分析这么多