tapable源码分析

1,819 阅读11分钟

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

  1. 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));
    }
}
  1. 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;
        }
}
  1. 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); 
        }
    }
}
  1. 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();
  1. 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捕获
  1. 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 暂时先分析这么多

参考资料