上面每一个 tapPromise 注册事件的事件处理函数都返回一个 Promise 实例,并将返回值传入 resolve 方法,调用 promise 方法触发事件时,如果所有事件处理函数返回的 Promise 实例结果都成功,会将结果存储在数组中,并作为参数传递给 promise 的 then 方法中成功的回调,如果有一个失败就是将失败的结果返回作为参数传递给失败的回调。
// 模拟 AsyncParallelHook 类 tapPromise/promise class AsyncParallelHook { constructor(args) { this.args = args; this.tasks = []; } tapPromise(name, task) { this.tasks.push(task); } promise(...args) { // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined args = args.slice(0, this.args.length); // 将所有事件处理函数转换成 Promise 实例,并发执行所有的 Promise return Promise.all(this.tasks.map(task => task(...args))); } }
其实根据上面对于 tapPromise 和 promise 使用的描述就可以猜到, promise 方法的逻辑是通过 Promise.all 来实现的。
2、AsyncSeriesHook
AsyncSeriesHook 为异步串行执行,与 AsyncParallelHook 相同,通过 tapAsync 注册的事件,通过 callAsync 触发,通过 tapPromise 注册的事件,通过 promise 触发,可以调用 then 方法。
(1) tapAsync/callAsync
与 AsyncParallelHook 的 callAsync 方法类似, AsyncSeriesHook 的 callAsync 方法也是通过传入回调函数的方式,在所有事件处理函数执行完毕后执行 callAsync 的回调函数。
// AsyncSeriesHook 钩子:tapAsync/callAsync 的使用 const { AsyncSeriesHook } = require("tapable"); // 创建实例 let asyncSeriesHook = new AsyncSeriesHook(["name", "age"]); // 注册事件 console.time("time"); asyncSeriesHook.tapAsync("1", (name, age, next) => { settimeout(() => { console.log("1", name, age, new Date()); next(); }, 1000); }); asyncSeriesHook.tapAsync("2", (name, age, next) => { settimeout(() => { console.log("2", name, age, new Date()); next(); }, 2000); }); asyncSeriesHook.tapAsync("3", (name, age, next) => { settimeout(() => { console.log("3", name, age, new Date()); next(); console.timeEnd("time"); }, 3000); }); // 触发事件,让监听函数执行 asyncSeriesHook.callAsync("panda", 18, () => { console.log("complete"); }); // 1 panda 18 2018-08-07T14:40:52.896Z // 2 panda 18 2018-08-07T14:40:54.901Z // 3 panda 18 2018-08-07T14:40:57.901Z // complete // time: 6008.790ms
异步串行是指,事件处理函数内三个定时器的异步执行时间分别为 1s 、 2s 和 3s ,而三个事件处理函数执行完总共用时接近 6s ,所以三个事件处理函数执行是需要排队的,必须一个一个执行,当前事件处理函数执行完才能执行下一个。
AsyncSeriesHook 类的 tabAsync 方法注册的事件处理函数参数中的 next 可以与 AsyncParallelHook 类中 tabAsync 方法参数的 done 进行类比,同为回调函数,不同点在于 AsyncSeriesHook 与 AsyncParallelHook 的 callAsync 方法的 “并行” 和 “串行” 的实现方式。
// 模拟 AsyncSeriesHook 类:tapAsync/callAsync class AsyncSeriesHook { constructor(args) { this.args = args; this.tasks = []; } tabAsync(name, task) { this.tasks.push(task); } callAsync(...args) { // 先取出最后传入的回调函数 let finalCallback = args.pop(); // 传入参数严格对应创建实例传入数组中的规定的参数,执行时多余的参数为 undefined args = args.slice(0, this.args.length); // 定义一个 i 变量和 next 函数,每次取出一个事件处理函数执行,并维护 i 的值 // 直到所有事件处理函数都执行完,调用 callAsync 的回调 // 如果事件处理函数中没有调用 next,则无法继续 let i = 0; let next = () => { let task = this.tasks[i++]; task ? task(...args, next) : finalCallback(); }; next(); } }
AsyncParallelHook 是通过循环依次执行了所有的事件处理函数, done 方法只为了检测是否已经满足条件执行 callAsync 的回调,如果中间某个事件处理函数没有调用 done ,只是不会调用 callAsync 的回调,但是所有的事件处理函数都执行了。
而 AsyncSeriesHook 的 next 执行机制更像 Express 和 Koa 中的中间件,在注册事件的回调中如果不调用 next ,则在触发事件时会在没有调用 next 的事件处理函数的位置 “卡死”,即不会继续执行后面的事件处理函数,只有都调用 next 才能继续,而最后一个事件处理函数中调用 next 决定是否调用 callAsync 的回调。
(2) tapPromise/promise
与 AsyncParallelHook 类似, tapPromise 注册事件的事件处理函数需要返回一个 Promise 实例, promise 方法最后也返回一个 Promise 实例。