浅谈Webpack核心模块tapable解析(3)

在上面代码中可以看到 SyncLoopHook 类 call 方法的实现更像是 SyncHook 和 SyncBailHook 的 call 方法的结合版,外层循环整个 tasks 事件处理函数队列,内层通过返回值进行循环,控制每一个事件处理函数的执行次数。

注意:在 Sync 类型 “钩子” 下执行的插件都是顺序执行的,只能使用 tab 注册。

Async 类型的钩子

Async 类型可以使用 tap 、 tapSync 和 tapPromise 注册不同类型的插件 “钩子”,分别通过 call 、 callAsync 和 promise 方法调用,我们下面会针对 AsyncParallelHook 和 AsyncSeriesHook 的 async 和 promise 两种方式分别介绍和模拟。

1、AsyncParallelHook

AsyncParallelHook 为异步并行执行,通过 tapAsync 注册的事件,通过 callAsync 触发,通过 tapPromise 注册的事件,通过 promise 触发(返回值可以调用 then 方法)。

(1) tapAsync/callAsync

callAsync 的最后一个参数为回调函数,在所有事件处理函数执行完毕后执行。

// AsyncParallelHook 钩子:tapAsync/callAsync 的使用 const { AsyncParallelHook } = require("tapable"); // 创建实例 let asyncParallelHook = new AsyncParallelHook(["name", "age"]); // 注册事件 console.time("time"); asyncParallelHook.tapAsync("1", (name, age, done) => { settimeout(() => { console.log("1", name, age, new Date()); done(); }, 1000); }); asyncParallelHook.tapAsync("2", (name, age, done) => { settimeout(() => { console.log("2", name, age, new Date()); done(); }, 2000); }); asyncParallelHook.tapAsync("3", (name, age, done) => { settimeout(() => { console.log("3", name, age, new Date()); done(); console.timeEnd("time"); }, 3000); }); // 触发事件,让监听函数执行 asyncParallelHook.callAsync("panda", 18, () => { console.log("complete"); }); // 1 panda 18 2018-08-07T10:38:32.675Z // 2 panda 18 2018-08-07T10:38:33.674Z // 3 panda 18 2018-08-07T10:38:34.674Z // complete // time: 3005.060ms

异步并行是指,事件处理函数内三个定时器的异步操作最长时间为 3s ,而三个事件处理函数执行完成总共用时接近 3s ,所以三个事件处理函数是几乎同时执行的,不需等待。

所有 tabAsync 注册的事件处理函数最后一个参数都为一个回调函数 done ,每个事件处理函数在异步代码执行完毕后调用 done 函数,则可以保证 callAsync 会在所有异步函数都执行完毕后执行,接下来看一看 callAsync 是如何实现的。

// 模拟 AsyncParallelHook 类:tapAsync/callAsync class AsyncParallelHook { 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 变量和 done 函数,每次执行检测 i 值和队列长度,决定是否执行 callAsync 的回调函数 let i = 0; let done = () => { if (++i === this.tasks.length) { finalCallback(); } }; // 依次执行事件处理函数 this.tasks.forEach(task => task(...args, done)); } }

在 callAsync 中,将最后一个参数(所有事件处理函数执行完毕后执行的回调)取出,并定义 done 函数,通过比较 i 和存储事件处理函数的数组 tasks 的 length 来确定回调是否执行,循环执行每一个事件处理函数并将 done 作为最后一个参数传入,所以每个事件处理函数内部的异步操作完成时,执行 done 就是为了检测是不是该执行 callAsync 的回调,当所有事件处理函数均执行完毕满足 done 函数内部 i 和 length 相等的条件时,则调用 callAsync 的回调。

(2) tapPromise/promise

要使用 tapPromise 注册事件,对事件处理函数有一个要求,必须返回一个 Promise 实例,而 promise 方法也返回一个 Promise 实例, callAsync 的回调函数在 promise 方法中用 then 的方式代替。

// AsyncParallelHook 钩子:tapPromise/promise 的使用 const { AsyncParallelHook } = require("tapable"); // 创建实例 let asyncParallelHook = new AsyncParallelHook(["name", "age"]); // 注册事件 console.time("time"); asyncParallelHook.tapPromise("1", (name, age) => { return new Promise((resolve, reject) => { settimeout(() => { console.log("1", name, age, new Date()); resolve("1"); }, 1000); }); }); asyncParallelHook.tapPromise("2", (name, age) => { return new Promise((resolve, reject) => { settimeout(() => { console.log("2", name, age, new Date()); resolve("2"); }, 2000); }); }); asyncParallelHook.tapPromise("3", (name, age) => { return new Promise((resolve, reject) => { settimeout(() => { console.log("3", name, age, new Date()); resolve("3"); console.timeEnd("time"); }, 3000); }); }); // 触发事件,让监听函数执行 asyncParallelHook.promise("panda", 18).then(ret => { console.log(ret); }); // 1 panda 18 2018-08-07T12:17:21.741Z // 2 panda 18 2018-08-07T12:17:22.736Z // 3 panda 18 2018-08-07T12:17:23.739Z // time: 3006.542ms // [ '1', '2', '3' ]

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/551c9a6189c76cd343c6b7ba5a84e388.html