学习webpack
源码时,老是绕不开tapable
,越看越以为它晦涩难懂,但只要理解了它的功能,学习就会容易不少。webpack
简单来讲,有一系列的同步、异步任务,我但愿它们能够以多种流程执行,好比:web
而tapable
库,就帮咱们实现了多种任务的执行流程,它们能够根据如下特色分类:面试
task
是否包含异步代码task
是否有执行顺序task
是否有数据依赖举个例子,若是咱们想要多个同步的任务 串行执行,只须要三个步骤:初始化hook、添加任务、触发任务执行:设计模式
// 引入 同步 的hook const { SyncBailHook } = require("tapable"); // 初始化 const tasks = new SyncBailHook(['tasks']) // 绑定一个任务 tasks.tap('task1', () => { console.log('task1', name); }) // 再绑定一个任务 tasks.tap('task2', () => { console.log('task2', name); }) // 调用call,咱们的两个任务就会串行执行了, tasks.call('done')
是否是很简单,下面咱们学习下tapable
实现了哪些任务执行流程,而且是如何实现的:promise
如上例子所示,每一种hook
都会有两个方法,用于添加任务和触发任务执行。在同步的hook
中,分别对应tap
和call
方法。并发
全部任务一块儿执行
class SyncHook { constructor() { // 用于保存添加的任务 this.tasks = [] } tap(name, task) { // 注册事件 this.tasks.push(task) } call(...args) { // 把注册的事件依次调用,无特殊处理 this.tasks.forEach(task => task(...args)) } }
若是其中一个task
有返回值(不为undefined
),就会中断tasks的调用
class SyncBailHook { constructor() { // 用于保存添加的任务 this.tasks = [] } tap(name, task) { this.tasks.push(task) } call(...args) { for (let i = 0; i < this.tasks.length; i++) { const result = this.tasks[i](...args) // 有返回值的话,就会中断调用 if (result !== undefined) { break } } } }
task
的计算结果会做为下一个task
的参数,以此类推
class SyncWaterfallHook { constructor() { this.tasks = [] } tap(name, task) { this.tasks.push(task) } call(...args) { const [first, ...others] = this.tasks const result = first(...args) // 上一个task的返回值会做为下一个task的函数参数 others.reduce((result, task) => { return task(result) }, result) } }
若是task
有返回值(返回值不为undefined
),就会循环执行当前task
,直到返回值为undefined
才会执行下一个task
class SyncLoopHook { constructor() { this.tasks = [] } tap(name, task) { this.tasks.push(task) } call(...args) { // 当前执行task的index let currentTaskIdx = 0 while (currentTaskIdx < this.tasks.length) { let task = this.tasks[currentTaskIdx] const result = task(...args) // 只有返回为undefined的时候才会执行下一个task,不然一直执行当前task if (result === undefined) { currentTaskIdx++ } } } }
异步事件流中,绑定和触发的方法都会有两种实现:异步
promise
:tapPromise
绑定、promise
触发promise
: tapAsync
绑定、callAsync
触发既然咱们要控制异步tasks
的执行流程,那咱们必需要知道它们执行完的时机:async
使用promise
的hook
,任务中resolve
的调用就表明异步执行完毕了;函数
// 使用promise方法的例子 // 初始化异步并行的hook const asyncHook = new AsyncParallelHook('async') // 添加task // tapPromise须要返回一个promise asyncHook.tapPromise('render1', (name) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('render1', name); resolve() }, 1000); }) }) // 再添加一个task // tapPromise须要返回一个promise asyncHook.tapPromise('render2', (name) => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('render2', name); resolve() }, 1000); }) }) // 传入的两个异步任务就能够串行执行了,并在执行完毕后打印done asyncHook.promise().then( () => { console.log('done'); })
但在使用非promise
的hook
时,异步任务执行完毕的时机咱们就无从获取了。因此咱们规定传入的 task
的最后一个参数参数为一个函数,而且在异步任务执行完毕后执行它,这样咱们能获取执行完毕的时机,以下例所示:oop
const asyncHook = new AsyncParallelHook('async') // 添加task asyncHook.tapAsync('example', (data, cb) => { setTimeout(() => { console.log('example', name); // 在异步操做完成时,调用回调函数,表示异步任务完成 cb() }, 1000); }) // 添加task asyncHook.tapAsync('example1', (data, cb) => { setTimeout(() => { console.log('example1', name); // 在异步操做完成时,调用回调函数,表示异步任务完成 cb() }, 1000); }) // 传入的两个异步任务就能够串行执行了,并在执行完毕后打印done asyncHook.callAsync('done', () => { console.log('done') })
task
一块儿执行,全部异步事件执行完成后,执行最后的回调。相似promise.all
NOTE: callAsync
中计数器的使用,相似于promise.all
的实现原理
class AsyncParallelHook { constructor() { this.tasks = [] } tapAsync(name, task) { this.tasks.push(task) } callAsync(...args) { // 最后一个参数为,流程结束的回调 const finalCB = args.pop() let index = 0 // 这就是每一个task执行完成时调用的回调函数 const CB = () => { ++index // 当这个回调函数调用的次数等于tasks的个数时,说明任务都执行完了 if (index === this.tasks.length) { // 调用流程结束的回调函数 finalCB() } } this.tasks.forEach(task => task(...args, CB)) } // task是一个promise生成器 tapPromise(name, task) { this.tasks.push(task) } // 使用promise.all实现 promise(...args) { const tasks = this.tasks.map(task => task(...args)) return Promise.all(tasks) } }
全部tasks
串行执行,一个tasks
执行完了在执行下一个
NOTE:callAsync
的实现与使用,相似于generate
执行器co
和async await
的原理
NOTE:promise
的实现与使用,就是面试中常见的 异步任务调度题 的正解。好比,实现每隔一秒打印1次,打印5次。
class AsyncSeriesHook { constructor() { this.tasks = [] } tapAsync(name, task) { this.tasks.push(task) } callAsync(...args) { const finalCB = args.pop() let index = 0 // 这就是每一个task异步执行完毕以后调用的回调函数 const next = () => { let task = this.tasks[index++] if (task) { // task执行完毕以后,会调用next,继续执行下一个task,造成递归,直到任务所有执行完 task(...args, next) } else { // 任务完毕以后,调用流程结束的回调函数 finalCB() } } next() } tapPromise(name, task) { this.tasks.push(task) } promise(...args) { let [first, ...others] = this.tasks return others.reduce((p, n) =>{ // then函数中返回另外一个promise,能够实现promise的串行执行 return p.then(() => n(...args)) },first(...args)) } }
异步task
串行执行,task
的计算结果会做为下一个task
的参数,以此类推。task
执行结果经过cb
回调函数向下传递。
class AsyncWaterfallHook { constructor() { this.tasks = [] } tapAsync(name, task) { this.tasks.push(task) } callAsync(...args) { const [first] = this.tasks const finalCB = args.pop() let index = 1 // 这就是每一个task异步执行完毕以后调用的回调函数,其中ret为上一个task的执行结果 const next = (error, ret) => { if(error !== undefined) { return } let task = this.tasks[index++] if (task) { // task执行完毕以后,会调用next,继续执行下一个task,造成递归,直到任务所有执行完 task(ret, next) } else { // 任务完毕以后,调用流程结束的回调函数 finalCB(ret) } } first(...args, next) } tapPromise(name, task) { this.tasks.push(task) } promise(...args) { let [first, ...others] = this.tasks return others.reduce((p, n) =>{ // then函数中返回另外一个promise,能够实现promise的串行执行 return p.then(() => n(...args)) }, first(...args)) } }
学了tapable
的一些hook
,你能扩展到不少东西:
promise.all
co
模块async await
你均可以去实现,用于巩固和拓展相关知识。
咱们在学习tapable
时,重点不在于这个库的细节和使用,而在于多个任务有可能的执行流程以及流程的实现原理,它们是众多实际问题的抽象模型,掌握了它们,你就能够在实际开发中和面试中触类旁通,举重若轻。