Appearance
AsyncSeriesHook
AsyncSeriesHook 为异步串行执行的。和我们上面的 AsyncParallelHook 一样,通过使用 tapAsync 注册事件,通过 callAsync 触发事件,也可以通过 tapPromise 注册事件,使用 promise 来触发。
例子
js
const { AsyncSeriesHook } = require('tapable');
// 创建一个同步事件流实例对象
const h1 = new AsyncSeriesHook(['xxx', 'arg2']);
h1.intercept({
call: (source, target, routesList) => {
console.log('Starting to h1 routes');
},
register: tapInfo => {
// tapInfo = { type: "promise", name: "GoogleMapsPlugin", fn: ... }
console.log(`${tapInfo.name} is doing its job`);
return tapInfo; // may return a new tapInfo object
},
tap: tap => {
console.log(tap, '444444');
},
});
// 添加同步事件A
h1.tapAsync('A', (name, age, done) => {
setTimeout(() => {
console.log('A', name, age, new Date().getSeconds());
done();
}, 2000);
});
// 添加同步事件B
h1.tapAsync('B', (name, age, done) => {
setTimeout(() => {
console.log('B', name, age, new Date().getSeconds());
done();
}, 2000);
});
// 添加同步事件C
h1.tapAsync('C', (name, age, done) => {
setTimeout(() => {
console.log('C', name, age, new Date().getSeconds());
done();
}, 2000);
});
// 添加同步事件F
h1.tapAsync(
{
name: 'F',
before: 'D',
},
(name, age, done) => {
setTimeout(() => {
console.log('F', name, age, new Date().getSeconds());
done();
}, 2000);
}
);
// 添加同步事件E
h1.tapAsync(
{
name: 'E',
before: 'C',
},
(name, age, done) => {
setTimeout(() => {
console.log('E', name, age, new Date().getSeconds());
done();
}, 2000);
}
);
// 添加同步事件D
h1.tapAsync('D', (name, age, done) => {
setTimeout(() => {
console.log('D', name, age, new Date().getSeconds());
done();
}, 2000);
});
// 执行这个事件流
h1.callAsync('立早', 20, () => {
console.log('函数执行完毕');
});
结果为
[HMR] Waiting for update signal from WDS...
A is doing its job
B is doing its job
C is doing its job
F is doing its job
E is doing its job
D is doing its job
Starting to h1 routes
{type: "async", fn: ƒ, name: "F", before: "D"} "444444"
{type: "async", fn: ƒ, name: "A"} "444444"
{type: "async", fn: ƒ, name: "B"} "444444"
{type: "async", fn: ƒ, name: "E", before: "C"} "444444"
{type: "async", fn: ƒ, name: "C"} "444444"
{type: "async", fn: ƒ, name: "D"} "444444"
[WDS] Hot Module Replacement enabled.
F 立早 20 21
{type: "async", fn: ƒ, name: "A"} "444444"
A 立早 20 23
{type: "async", fn: ƒ, name: "B"} "444444"
B 立早 20 25
{type: "async", fn: ƒ, name: "E", before: "C"} "444444"
E 立早 20 27
{type: "async", fn: ƒ, name: "C"} "444444"
C 立早 20 29
{type: "async", fn: ƒ, name: "D"} "444444"
D 立早 20 31
函数执行完毕
发现其秒数都是间隔 2 秒,那么说明所有的事件流是串行执行的,下一个事件流需要等待上一个事件流的完成才会执行
原理
this.content()
js
class AsyncSeriesHookCodeFactory extends HookCodeFactory {
content({ onError, onDone }) {
return this.callTapsSeries({
onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
onDone,
});
}
}
发现其直接通过 this.callTapsSeries()去生成可以执行的代码的,但是其与 syncHook 等不同的地方在于其 option.type === 'async'
那么这时候我们就可以看到 callTap 的 async 部分了
js
class HookCodeFactory {
/**
* **核心**
* 生成每一个事件流执行的代码字符串
*
*/
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) {
// 原来所有的 taps是存放在 this._taps 中,下面需要多次访问那么为了提高性能就缓存一份 var _tap0 = _taps[0];
// 在上面的例子中可能不能看出问题,主要是当我们注册很多拦截器的时候
// 这时候就是
/*
var _tap0 = _taps[0];
_interceptors[0].tap(_tap0);
_interceptors[1].tap(_tap0);
_interceptors[2].tap(_tap0);
_interceptors[3].tap(_tap0);
var _fn0 = _x[0];
_fn0(xxx, arg2);
*/
if (!hasTapCached) {
code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`;
hasTapCached = true;
}
code += `${this.getInterceptor(i)}.tap(${interceptor.context ? '_context, ' : ''}_tap${tapIndex});\n`;
}
}
// 生成 var _fn0 = _x[0];
code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
const tap = this.options.taps[tapIndex];
// 下面生成 事件流的回调函数执行字符串
// 最简单的是 _fn0(xxx, arg2);
switch (tap.type) {
case 'sync':
case 'async':
/*
异步回调的核心是:
1. 生成cbCode ,异步回调 (name, age, done) => {} 中done的
_err2 => {
if (_err2) {
_callback(_err2);
} else {
// 然后在此处调用 onDone() 去生成下一个事件流的伪代码
}
}
这样就会变成
_fn(参数 , 最后一个就是上面cbCode)
*/
// 生成异步回调后执行的伪代码 即done()后执行的代码
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}`);
}
// 在异步回调伪代码中的else 即成功中插入下一个事件流的伪代码
if (onDone) {
cbCode += onDone();
}
cbCode += '}\n';
cbCode += '}';
// 生成当前事件流执行的伪代码,前面异步回调done()的伪代码作为after成为最后一个参数
code += `_fn${tapIndex}(${this.args({
before: tap.context ? '_context' : undefined,
after: cbCode,
})});\n`;
break;
case 'promise':
break;
}
return code;
}
/**
* 生成串行的事件流执行代码
* syncHook 也是串行处理的 其没有重写 onResult,所以在每一个事件流执行后都是直接执行 done()(next(i+1))
* SyncBailHook 也是串行处理的 但是其与syncHook的区别是 如果事件处理函数执行时有一个返回值不为空(即返回值为 undefined),则跳过剩下未执行的事件处理函数
*
*
*/
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();
};
// 核心还是通过 this.callTap()去生成
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);
}
}
从上面可以看书对于异步 async 的处理方式是,通过 cbCode 生成异步回调的伪代码
js
_err1 => {
if (_err1) {
_callback(_err1);
} else {
// 执行onDone() 插入下一个事件流生成伪代码
}
};
然后将这个伪代码 cbCode 作为事件流执行函数的最后一个参数,那么这样伪代码就变成了
js
_fn5(xxx, arg2, _err5 => {
if (_err5) {
_callback(_err5);
} else {
}
});
这样我们在 setTimeout 执行的时候 触发 done() 然后就会执行_err5 => {}
这段,然后在里面判断是否存在_err5
done('加载失败了') : 传入_err5 那么就会直接执行
_callback(_err5);
即执行 h1.callAsync('立早', 20, () => {})的回调done():说明加载成功了,那么就是执行
else{}
部分这里面插入的是下一个事件流的伪代码,那就执行下一个事件流了
js
h1.tapAsync('C', (name, age, done) => {
setTimeout(() => {
console.log('C', name, age, new Date().getSeconds());
done();
}, 2000);
});
从上面的流程可以看出其整体的过程是这样的
全部伪代码
js
// 第一步: 生成事件流A的伪代码,先生成拦截器的tap
var _tap0 = _taps[0];
_interceptors[0].tap(_tap0);
var _fn0 = _x[0];
//
_fn0(xxx, arg2, _err0 => {
// 1.1 cbCode 生成 _err0 => { 这时候这个在内存中
if (_err0) {
_callback(_err0);
} else {
// 1.2 中触发下一个事件流的处理
var _tap1 = _taps[1];
_interceptors[0].tap(_tap1);
var _fn1 = _x[1];
_fn1(xxx, arg2, _err1 => {
if (_err1) {
_callback(_err1);
} else {
var _tap2 = _taps[2];
_interceptors[0].tap(_tap2);
var _fn2 = _x[2];
_fn2(xxx, arg2, _err2 => {
if (_err2) {
_callback(_err2);
} else {
var _tap3 = _taps[3];
_interceptors[0].tap(_tap3);
var _fn3 = _x[3];
_fn3(xxx, arg2, _err3 => {
if (_err3) {
_callback(_err3);
} else {
var _tap4 = _taps[4];
_interceptors[0].tap(_tap4);
var _fn4 = _x[4];
_fn4(xxx, arg2, _err4 => {
if (_err4) {
_callback(_err4);
} else {
var _tap5 = _taps[5];
_interceptors[0].tap(_tap5);
var _fn5 = _x[5];
_fn5(xxx, arg2, _err5 => {
if (_err5) {
_callback(_err5);
} else {
_callback();
}
});
}
});
}
});
}
});
}
});
}
});