Skip to content

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();
											}
										});
									}
								});
							}
						});
					}
				});
			}
		});
	}
});

上次更新: