内容简介:好的,方法一共是上述这么多,第一眼看过去,懵逼树下你和我,所以我们还是一点点来,一个个的分析、学习和了解先来个使用的例子,例如前端开发者需要掌握哪些技能?ok,就是上面这两句,我们创建了个FontEnd前端开发
- tapable是个独立的库
- webpack中大量使用了这个库
- tapable主要是用来处理事件,解决的问题有点类似EventEmitter,不过功能更加强大
Q2:tapable方法有哪些?
const { SyncHook, SyncBailHook, SyncWaterfallHook, SyncLoopHook, AsyncParallelHook, AsyncParallelBailHook, AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook } = require("tapable"); 复制代码
好的,方法一共是上述这么多,第一眼看过去,懵逼树下你和我,所以我们还是一点点来,一个个的分析、学习和了解
Q3:啥是SyncHook?
先来个使用的例子,例如前端开发者需要掌握哪些技能?
step1:首先我们要明确群体是前端开发
const {SyncHook}= require('tapable'); const FontEnd = new SyncHook(); 复制代码
ok,就是上面这两句,我们创建了个FontEnd前端开发
step2:前端开发需要掌握哪些技能,例如webpack、react对吧
FontEnd.tap('webpack',()=>{ console.log("get webpack") }); FontEnd.tap('react',()=>{ console.log("get react") }); 复制代码
ok,上面的tap就是用来绑定事件的,为前端开发添加了两个技能
step3:技能需要学习才能掌握,所以我们要有学习的动作
FontEnd.learn=()=>{ FontEnd.call() }; FontEnd.learn(); 复制代码
step4:查看执行结果
get webpack get react 复制代码
可以看到,通过上面的调用,我们的前端开发已经学会了react、webpack
step5:传参
前面知道FontEnd这个群体,需要学react、webpack,但落到个人角度,究竟哪一个开发者掌握这些技能了呢?
const {SyncHook}= require('tapable'); const FontEnd = new SyncHook(); FontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack") }); FontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FontEnd.start=(name)=>{ FontEnd.call(name) }; FontEnd.start('xiaoming'); 复制代码
修改前面的代码,添加参数,预期是输出xxx get react
step6: 查看输出结果
undefined get webpack undefined get react 复制代码
最终结果是undefined,也就是参数没传进去
step7:为SyncHook添加约定参数
这是因为 const FontEnd = new SyncHook();
创建SyncHook的时候没有约定参数,只要为其添加参数即可,如下:
const {SyncHook}= require('tapable'); const FontEnd = new SyncHook(['name']);// 添加参数约定 FontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack") }); FontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FontEnd.start=(name)=>{ FontEnd.call(name) }; FontEnd.start('xiaoming'); 复制代码
最终输出:
xiaoming get webpack xiaoming get react 复制代码
SyncHook总结
- SyncHook目前来看比较像订阅发布
- 就像jquery中的add、fire方法,只不过这里是tap、call
Q4:SyncHook如何实现?
SyncHook实现比较简单,就是最简单的订阅发布
class SyncHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tap(name,task){ this.tasks.push(task); } call(...args){ const param = args.slice(0,this.limit.length); this.tasks.forEach(item=>item(...param)); } } 复制代码
- limit是用来做参数校验的
- tasks用来收集订阅
- tap方法用来想tasks中添加方法
- call方法,先检验参数,然后再执行所有的已订阅方法
总结:原理比较简单,没有太多技术含量,主要就是一个同步的钩子函数
Q5:啥是SyncBailHook?
熔断机制,如果前一个事件 return true
,则不再执行下一个,还是前面的例子:
const {SyncBailHook} =require('tapable'); const FontEnd = new SyncBailHook(['name']); FontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack ") }); FontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FontEnd.start=(...args)=>{ FontEnd.call(...args) }; FontEnd.start('xiaoming'); 复制代码
此时,把函数从SyncHook换成SyncBailHook,执行的结果没有任何区别
but,思考一下,学习很容易会学不下去,所以修改一下我们的例子:
const {SyncBailHook} =require('tapable'); const FontEnd = new SyncBailHook(['name']); FontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack ") return '学不动了啊!'; }); FontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FontEnd.start=(...args)=>{ FontEnd.call(...args) }; FontEnd.start('xiaoming'); 复制代码
此时仅输出:
xiaoming get webpack 复制代码
后面的react没有执行
总结:
- SyncBailHook主要解决的问题是条件阻塞
- 当订阅事件符合某一判断时,不再执行下面的流程
- 应用场景,场景不断深入的场景,a、a+b、a+b+c、a+b+c+d这种场景
Q6:SyncBailHook如何实现?
SyncBailHook也十分简单,还是之前那个例子:
class SyncBailHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tap(name,task){ this.tasks.push(task); } call(...args){ const param = args.slice(0,this.limit.length); this.tasks.some(item=>item(...param));// 只改了一行 } } 复制代码
可以看到,和上面SyncHook十分相似,无非就是把执行函数forEach,换成some,因为some是阻塞式执行,当返回true,则不会执行后面的内容
Q7:啥是SyncWaterfullHook?
还是先来个使用的例子,例如前端,技能都是一个个学的,要学完webpack再学react,例如:
const {SyncWaterfallHook} = require('tapable'); const FontEnd = new SyncWaterfallHook(['name']); FontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack ") return '学完webpack了,该学react了'; }); FontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FontEnd.start=(...args)=>{ FontEnd.call(...args) }; FontEnd.start('xiaoming'); 复制代码
此时输出:
xiaoming get webpack 学完webpack了,该学react了 get react 复制代码
- SyncWaterfallHook会将前一个任务的执行结果,传递给后一个
- 主要使用场景是处理逻辑之间相互依赖
- 实际效果和redux中的compose方法一毛一样
Q8:SyncWaterfullHook如何实现?
class SyncWaterfallHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tap(name,task){ this.tasks.push(task); } call(...args){ const param = args.slice(0,this.limit.length); const [first,...others] = this.tasks; const ret = first(...param); others.reduce((pre,next)=>{ return next(pre); },ret) } } 复制代码
SyncWaterfallHook实现也比较简单
- 完全按照redux的compose来实现就行
- 第一步,取出第一个执行,并拿到结果ret
- 第二步,将结果ret,当作reduce的参数传递进去
- 第三步,遍历,不断把参数传给下一个函数
总结:SyncWaterfallHook主要还是用于函数之间对结果存在依赖的场景
Q9:啥是SyncLoopHook?
还是前面的例子,如果一次学不懂一门技术,那就要多学几遍,例如:
const FontEnd = new SyncLoopHook(['name']); let num = 0; FontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack ") return ++num === 3?undefined:'再学一次'; }); FontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FontEnd.start=(...args)=>{ FontEnd.call(...args) }; FontEnd.start('xiaoming'); 复制代码
上面执行的结果是:
xiaoming get webpack xiaoming get webpack xiaoming get webpack xiaoming get react 复制代码
- SyncLoopHook任务能够执行多次
- 返回undefined则停止执行,返回非undefined则继续执行当前任务
总结:主要场景是同一任务,需要执行多次
Q10:SyncLoopHook如何实现?
class SyncLoopHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tap(name,task){ this.tasks.push(task); } call(...args){ const param = args.slice(0,this.limit.length); let index = 0; while(index<this.tasks.length){ const result = this.tasks[index](...param); if(result === undefined){ index++; } } } } 复制代码
- 上面的实现是通过计数
- 如果结果不为undefined则下标index不移动
- 如果结果为undefined则下标index增加
也可以换doWhile来实现
class SyncLoopHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tap(name,task){ this.tasks.push(task); } call(...args){ const param = args.slice(0,this.limit.length); this.tasks.forEach(task=>{ let ret; do{ ret = task(...param); }while(ret!=undefined) }) } } 复制代码
- 这种实现没有下标概念了
- 直接遍历tasks任务组,如果任务组中某一个任务执行的结果不是undefined则再次执行
总结:SyncLoopHook这个使用场景相对较少,不过了解一下也好
Q11:啥是AsyncParralleHook?
前面了解的都是同步hook,更关键的是异步hook
举个例子,同学小王说去学前端了,但你也不知道他什么时候学完,只有他学完告诉你,你才知道他学完了,例:
const {AsyncParallelHook} = require('tapable'); const FontEnd = new AsyncParallelHook(['name']); FontEnd.tapAsync('webpack',(name,cb)=>{ setTimeout(() => { console.log(name+" get webpack ") cb(); }, 1000); }); FontEnd.tapAsync('react',(name,cb)=>{ setTimeout(() => { console.log(name+" get react") cb(); }, 1000); }); FontEnd.start=(...args)=>{ FontEnd.callAsync(...args,()=>{ console.log("end"); }) }; FontEnd.start('小王'); 复制代码
最终输出:
小王 get webpack 小王 get react end 复制代码
- AsyncParralleHook是异步并行钩子
- 使用场景,例如同时发起对两个接口的请求
- 注意:这次注册事件,不再是tap了,而是tapAsync
- 注意:这次的事件执行,不再是call了,而是callAsync
- 可以看出tapable中区分了同步、异步的订阅和发布
- 注意:想要让所有异步执行完成后,接收到通知,需要执行cb()
Q12:AsyncParralleHook如何实现?
class AsyncParallelHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapAsync(name,task){ this.tasks.push(task); } callAsync(...args){ const finalCallBack = args.pop(); const param = args.slice(0,this.limit.length); let index = 0; const done=()=>{ index++; if(index === this.tasks.length){ finalCallBack(); } } this.tasks.forEach(item=>item(...param,done)) } } 复制代码
- AsyncParallelHook最简单就是通过计数
- 在实例上添加一个计数器
- 然后遍历tasks,当任务成功个数与任务总数相同时,执行finalCallBack
总结:AsyncParallelHook解决的问题和promise.all类似,都是用于解决异步并行的问题
Q13:AsyncParralleHook(2)如何使用promise?
前面虽然用:AsyncParralleHook能够解决异步,但并没有使用primise,也没有类promise的概念
const {AsyncParallelHook} = require('tapable'); const FontEnd = new AsyncParallelHook(['name']); FontEnd.tapPromise('webpack',(name)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get webpack ") resolve(); }, 1000); }) }); FontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") resolve(); }, 1000); }) }); FontEnd.start=(...args)=>{ FontEnd.promise(...args).then(()=>{ console.log("end"); }) }; FontEnd.start('小王'); 复制代码
调用上面的api后,输出:
小王 get webpack 小王 get react end 复制代码
- 注意:此时绑定事件的方法叫做tapPromise
- 注意:此时执行事件的方法叫做promise
总结:
- tapable共有三种事件绑定方法:tap、tapAsync、tapPromise
- tapable共有三种事件执行方法:call、callAsync、promise
Q14:AsyncParralleHook(2)promise版如何实现?
class AsyncParallelHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapPromise(name,task){ this.tasks.push(task); } promise(...args){ const param = args.slice(0,this.limit.length); const tasks = this.tasks.map(task=>task(...param)); return Promise.all(tasks) } } 复制代码
- 核心就是实现两个方法,tapPromise和promise
- tapPromise其实和之前的tap没有明显区别(简单实现的问题)
- promise的话,其实就是返回一个Promise.all
Q15:啥是AsyncParallelBailHook?
AsyncParallelBailHook这个钩子和前面的钩子不太一样 按前面的例子来讲:
- 同学小王说去学前端了,但你也不知道他什么时候学完,只有他学完告诉你,你才知道他学完了
- 小王学了webpack,学崩了,告诉了你
- 你听说小王学崩了,你就以为他学不下去了,你就对大家伙说,小王学崩了
- 但是小王同时也学了react却咬牙学完了
- 虽然学完了,但你已经对外宣布小王崩了,很打脸,所以就当不知道了
这就是AsyncParallelBailHook处理的事情
const {AsyncParallelBailHook} = require('tapable'); const FontEnd = new AsyncParallelBailHook(['name']); FontEnd.tapPromise('webpack',(name)=>{ return new Promise((resolve,reject)=>{ setTimeout(() => { console.log(name+" get webpack ") reject('小王学崩了!'); }, 1000); }) }); FontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") resolve(); }, 2000); }) }); FontEnd.start=(...args)=>{ FontEnd.promise(...args).then(()=>{ console.log("end"); },(err)=>{ console.log("听说:",err) }) }; FontEnd.start('小王'); 复制代码
上面代码执行结果是:
小王 get webpack 听说: 小王学崩了! 小王 get react 复制代码
- 上面例子,第一个并行任务返回了reject
- reject只要不是undefined,就会直接进入promise.all的catch
- 异步任务,react还是会执行,但成功后没有处理了
再看一个例子:
const {AsyncParallelBailHook} = require('tapable'); const FontEnd = new AsyncParallelBailHook(['name']); FontEnd.tapPromise('webpack',(name)=>{ return new Promise((resolve,reject)=>{ setTimeout(() => { console.log(name+" get webpack ") reject(); }, 1000); }) }); FontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") resolve(); }, 2000); }) }); FontEnd.start=(...args)=>{ FontEnd.promise(...args).then(()=>{ console.log("end"); },(err)=>{ console.log("听说:",err) }) }; FontEnd.start('小王'); 复制代码
和上面就改了1行,就是reject内容为空,此时输出:
小王 get webpack 小王 get react end 复制代码
- 此时即便调用了reject也不会进入到catch
- reject返回空,后面的任务也会照常执行
总结:
- AsyncParallelBailHook,如果返回真值,则直接会走进catch
- 无论返回结果是什么,所有任务都会执行
- 主要场景是,并行请求3个接口,随便哪一个返回结果都行,只要返回了,就对返回进行处理(走catch)
- 如果用来处理同步,则和SyncBailHook效果一样
- 如果处理tapSync,则遇到return true最终的callback不会执行
- 如果处理promise,则遇到rejcet(true),则直接进入catch
Q16:AsyncParallelBailHook如何实现?
这个AsyncParallelBailHook真真烧脑了好一会
class AsyncParallelBailHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapPromise(name,task){ this.tasks.push(task); } promise(...args){ const param = args.slice(0,this.limit.length); const tasks = this.tasks.map(task=>{ return new Promise((resolve,reject)=>{ task(...param).then((data)=>{ resolve(data); },(err)=>{ err? reject(err):resolve(); }); }) }); return Promise.all(tasks) } } 复制代码
- 正常情况下,promise.all中任意一个任务reject,就会进入统一的catch
- 但我们需要的是根据reject的值来判断是否走如catch
- 所以我们在原有task外,再包一层promise
- 如果reject值为真,则执行reject
- 如果reject值为假,则执行resolve,就当什么也没发生
Q17:啥是AsyncSeriesHook?
前面讲的是异步并行,现在该说异步串行了,例如小王,学完webpack才去学的react,你也不知道他什么时候学完,但他学完一个就会告诉你一下,例:
const {AsyncSeriesHook} = require('tapable'); const FontEnd = new AsyncSeriesHook(['name']); console.time('webpack'); console.time('react'); FontEnd.tapPromise('webpack',(name,cb)=>{ return new Promise((resolve,reject)=>{ setTimeout(() => { console.log(name+" get webpack ") console.timeEnd('webpack'); resolve(); }, 1000); }) }); FontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") console.timeEnd('react'); resolve(); }, 1000); }) }); FontEnd.start=(...args)=>{ FontEnd.promise(...args).then(()=>{ console.log("end"); }) }; FontEnd.start('小王'); 复制代码
上面代码执行结果:
小王 get webpack webpack: 1010.781ms 小王 get react react: 2016.598ms end 复制代码
- 两个异步任务,变成了串行
- 从时间能够得出,两个1s的异步的任务,串行后总时间变成了2s
总结:AsyncSeriesHook解决的问题是异步串行,例如node的os.cpus()有限,可以把任务分批次执行,这样对性能有保障
Q18:AsyncSeriesHook如何实现?
class AsyncSeriesHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapPromise(name,task){ this.tasks.push(task); } promise(...args){ const param = args.slice(0,this.limit.length); const [first,...others] = this.tasks; return others.reduce((pre,next)=>{ return pre.then(()=>next(...param)) },first(...param)) } } 复制代码
- 实现核心就是promise串行
- 取出第一个任务,执行拿到promise实例,然后通过reduce遍历
Q19:啥是AsyncSeriesBailHook?
还是前面的例子,如果小王学前端,学了webapck就彻底放弃了,那后面的react也就不用学了
const {AsyncSeriesBailHook} = require('tapable'); const FontEnd = new AsyncSeriesBailHook(['name']); console.time('webpack'); console.time('react'); FontEnd.tapPromise('webpack',(name,cb)=>{ return new Promise((resolve,reject)=>{ setTimeout(() => { console.log(name+" get webpack ") console.timeEnd('webpack'); reject('小王彻底放弃了'); }, 1000); }) }); FontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") console.timeEnd('react'); resolve(); }, 1000); }) }); FontEnd.start=(...args)=>{ FontEnd.promise(...args).then(()=>{ console.log("end"); }).catch((err)=>{ console.log("err",err) }) }; FontEnd.start('小王'); 复制代码
上面代码输出:
小王 get webpack webpack: 1010.518ms err 小王彻底放弃了 复制代码
- 上面的代码只执行到webpack
- AsyncSeriesBailHook,任务如果return,或者reject,则阻塞了
场景:主要是异步串行,如果某一个任务执行的结果reject或者return,那么后面的都将不再执行
Q20:AsyncSeriesBailHook如何实现?
class AsyncSeriesBailHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapPromise(name,task){ this.tasks.push(task); } promise(...args){ const param = args.slice(0,this.limit.length); const [first,...others] = this.tasks; return new Promise((resolve,reject)=>{ others.reduce((pre,next,index,arr)=>{ return pre .then(()=>next(...param)) .catch((err=>{ arr.splice(index,arr.length-index); reject(err); })).then(()=>{ (index+1 === arr.length) && resolve(); }) },first(...param)) }) } } 复制代码
AsyncSeriesBailHook实现难度要高很多
- 首先在reduce外再包一层promise
- 当遇到任何一个子任务进入catch的时候,则将reduce的第四个参数arr切割,使其无法再向下进行,也就是停止reduce的继续
- 同时所有promise后面再添加一个后置then,用来检测是否全部执行完成
- 为什么使用index+1,是因为后置then肯定是最后一个任务,但遍历index还处于上一个下标,所以只要加1就好
Q21:啥是AsyncSeriesWaterfallHook?
SyncWaterFallHook前面已经了解过了,就是前一个执行完的结果会传递给下一个执行函数,和AsyncSeriesWaterfallHook的区别就是,一个是同步一个是异步
具体来说,例如只有一本教材,小王学完,小张才能学
const FontEnd = new AsyncSeriesWaterfallHook(['name']); FontEnd.tapAsync('webpack',(name,cb)=>{ setTimeout(() => { console.log(name+" get webpack ") cb(null,'小李'); }, 1000); }); FontEnd.tapAsync('webpack',(name,cb)=>{ setTimeout(() => { console.log(name+" get webpack ") cb(null,'小张'); }, 1000); }); FontEnd.tapAsync('webpack',(name,cb)=>{ setTimeout(() => { console.log(name+" get webpack ") cb(null,'小红'); }, 1000); }); FontEnd.start=(...args)=>{ FontEnd.callAsync(...args,(data)=>{ console.log("全学完了",) }) }; FontEnd.start('小王'); 复制代码
上面代码,最终输出:
小王 get webpack 小李 get webpack 小张 get webpack 全学完了 复制代码
总结:这个的用法和SyncWaterFallHook的用法一致
Q22:AsyncSeriesWaterfallHook如何实现?
class AsyncSeriesWaterfallHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapAsync(name,task){ this.tasks.push(task); } callAsync(...args){ const param = args.slice(0,this.limit.length); const finalCallBack = args.pop(); let index = 0; const next = (err,data)=>{ const task = this.tasks[index]; if(!task)return finalCallBack(); if(index === 0){ task(...param,next) }else{ task(data,next) } index++; } next(); } } 复制代码
- 主要是通过封装一个回调函数next
- 然后不断调用任务队列中的任务,调用的时候,再传递相同的回调函数进去
prmise版本的实现如下:
class AsyncSeriesWaterfallHook { constructor(limit = []){ this.limit= limit; this.tasks = []; } tapPromise(name,task){ this.tasks.push(task); } promise(...args){ const param = args.slice(0,this.limit.length); const [first,...others] = this.tasks; return others.reduce((pre,next)=>{ return pre.then((data)=>{ return data?next(data):next(...param); }) },first(...param)) } } 复制代码
- promise的实现要相对简单一些
- 主要去看then方法中是否有内容,如果有的话,则传递个下一个函数,如果没有,则用初始参数
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。