每个任务代码跟AsyncParallelHook是一样的,只是使用的Hook不一样,而最终效果的区别是:AsyncParallelHook所有任务同时开始,所以最终总耗时就是耗时最长的那个任务的耗时;AsyncSeriesHook的任务串行执行,下一个任务要等上一个任务完成了才能开始,所以最终总耗时是所有任务耗时的总和,上面这个例子就是1 + 2 + 2,也就是5秒:
AsyncSeriesBailHookAsyncSeriesBailHook就是在AsyncSeriesHook的基础上加上了Bail的逻辑,也就是中间任何一个任务返回不为undefined的值,终止执行,直接执行最后的回调,并且将这个返回值传给最终的回调:
const { AsyncSeriesBailHook } = require("tapable"); const accelerate = new AsyncSeriesBailHook(["newSpeed"]); console.time("total time"); // 记录起始时间 accelerate.tapAsync("LoggerPlugin", (newSpeed, done) => { // 1秒后加速才完成 setTimeout(() => { console.log("LoggerPlugin", `加速到${newSpeed}`); done(); }, 1000); }); accelerate.tapAsync("OverspeedPlugin", (newSpeed, done) => { // 2秒后检测是否超速 setTimeout(() => { if (newSpeed > 120) { console.log("OverspeedPlugin", "您已超速!!"); } // 这个任务的done返回一个错误 // 注意第一个参数是node回调约定俗成的错误 // 第二个参数才是Bail的返回值 done(null, new Error("您已超速!!")); }, 2000); }); accelerate.tapAsync("DamagePlugin", (newSpeed, done) => { // 2秒后检测是否损坏 setTimeout(() => { if (newSpeed > 300) { console.log("DamagePlugin", "速度实在太快,车子快散架了。。。"); } done(); }, 2000); }); accelerate.callAsync(500, (error, data) => { if (data) { console.log("任务执行出错:", data); } else { console.log("任务全部完成"); } console.timeEnd("total time"); // 记录总共耗时 });这个执行结果跟AsyncParallelBailHook的区别就是AsyncSeriesBailHook被阻断后,后面的任务由于还没开始,所以可以被完全阻断,而AsyncParallelBailHook后面的任务由于已经开始了,所以还会继续执行,只是结果已经不关心了。
AsyncSeriesWaterfallHookWaterfall的作用是将前一个任务的结果传给下一个任务,其他的跟AsyncSeriesHook一样的,直接来看代码吧:
const { AsyncSeriesWaterfallHook } = require("tapable"); const accelerate = new AsyncSeriesWaterfallHook(["newSpeed"]); console.time("total time"); // 记录起始时间 accelerate.tapAsync("LoggerPlugin", (newSpeed, done) => { // 1秒后加速才完成 setTimeout(() => { console.log("LoggerPlugin", `加速到${newSpeed}`); // 注意done的第一个参数会被当做error // 第二个参数才是传递给后面任务的参数 done(null, "LoggerPlugin"); }, 1000); }); accelerate.tapAsync("Plugin2", (data, done) => { setTimeout(() => { console.log(`上一个插件是: ${data}`); done(null, "Plugin2"); }, 2000); }); accelerate.tapAsync("Plugin3", (data, done) => { setTimeout(() => { console.log(`上一个插件是: ${data}`); done(null, "Plugin3"); }, 2000); }); accelerate.callAsync(500, (error, data) => { console.log("最后一个插件是:", data); console.timeEnd("total time"); // 记录总共耗时 });运行效果如下:
总结本文例子已经全部上传到GitHub,大家可以拿下来做个参考:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Engineering/tapable-usage
tapable是webpack实现plugin的核心库,他为webpack提供了多种事件处理和流程控制的Hook。
这些Hook主要有同步(Sync)和异步(Async)两种,同时还提供了阻断(Bail),瀑布(Waterfall),循环(Loop)等流程控制,对于异步流程还提供了并行(Paralle)和串行(Series)两种控制方式。
tapable其核心原理还是事件的发布订阅模式,他使用tap来注册事件,使用call来触发事件。
异步hook支持两种写法:回调和Promise,注册和触发事件分别使用tapAsync/callAsync和tapPromise/promise。
异步hook使用回调写法的时候要注意,回调函数的第一个参数默认是错误,第二个参数才是向外传递的数据,这也符合node回调的风格。
文章的最后,感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是作者持续创作的动力。
欢迎关注我的公众号进击的大前端第一时间获取高质量原创~
“前端进阶知识”系列文章:https://juejin.im/post/5e3ffc85518825494e2772fd
“前端进阶知识”系列文章源码GitHub地址: https://github.com/dennis-jiang/Front-End-Knowledges