tapable据说了好久,终于下定决心系统学习一下javascript
const { SyncHook, SyncBailHook, SyncWaterfallHook, SyncLoopHook, AsyncParallelHook, AsyncParallelBailHook, AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook } = require("tapable"); 复制代码
好的,方法一共是上述这么多,第一眼看过去,懵逼树下你和我,因此咱们仍是一点点来,一个个的分析、学习和了解前端
先来个使用的例子,例如前端开发者须要掌握哪些技能?java
const {SyncHook}= require('tapable'); const FrontEnd = new SyncHook(); 复制代码
ok,就是上面这两句,咱们建立了个FrontEnd前端开发node
FrontEnd.tap('webpack',()=>{ console.log("get webpack") }); FrontEnd.tap('react',()=>{ console.log("get react") }); 复制代码
ok,上面的tap就是用来绑定事件的,为前端开发添加了两个技能react
FrontEnd.learn=()=>{ FrontEnd.call() }; FrontEnd.learn(); 复制代码
get webpack
get react
复制代码
能够看到,经过上面的调用,咱们的前端开发已经学会了react、webpackjquery
前面知道FrontEnd这个群体,须要学react、webpack,但落到我的角度,究竟哪个开发者掌握这些技能了呢?webpack
const {SyncHook}= require('tapable'); const FrontEnd = new SyncHook(); FrontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack") }); FrontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FrontEnd.start=(name)=>{ FrontEnd.call(name) }; FrontEnd.start('xiaoming'); 复制代码
修改前面的代码,添加参数,预期是输出xxx get reactweb
undefined get webpack
undefined get react
复制代码
最终结果是undefined,也就是参数没传进去redux
这是由于const FrontEnd = new SyncHook();
建立SyncHook的时候没有约定参数,只要为其添加参数便可,以下:api
const {SyncHook}= require('tapable'); const FrontEnd = new SyncHook(['name']);// 添加参数约定 FrontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack") }); FrontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FrontEnd.start=(name)=>{ FrontEnd.call(name) }; FrontEnd.start('xiaoming'); 复制代码
最终输出:
xiaoming get webpack
xiaoming get react
复制代码
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)); } } 复制代码
总结:原理比较简单,没有太多技术含量,主要就是一个同步的钩子函数
熔断机制,若是前一个事件return true
,则再也不执行下一个,仍是前面的例子:
const {SyncBailHook} =require('tapable'); const FrontEnd = new SyncBailHook(['name']); FrontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack ") }); FrontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FrontEnd.start=(...args)=>{ FrontEnd.call(...args) }; FrontEnd.start('xiaoming'); 复制代码
此时,把函数从SyncHook换成SyncBailHook,执行的结果没有任何区别
but,思考一下,学习很容易会学不下去,因此修改一下咱们的例子:
const {SyncBailHook} =require('tapable'); const FrontEnd = new SyncBailHook(['name']); FrontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack ") return '学不动了啊!'; }); FrontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FrontEnd.start=(...args)=>{ FrontEnd.call(...args) }; FrontEnd.start('xiaoming'); 复制代码
此时仅输出:
xiaoming get webpack
复制代码
后面的react没有执行
总结:
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,则不会执行后面的内容
仍是先来个使用的例子,例如前端,技能都是一个个学的,要学完webpack再学react,例如:
const {SyncWaterfallHook} = require('tapable'); const FrontEnd = new SyncWaterfallHook(['name']); FrontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack ") return '学完webpack了,该学react了'; }); FrontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FrontEnd.start=(...args)=>{ FrontEnd.call(...args) }; FrontEnd.start('xiaoming'); 复制代码
此时输出:
xiaoming get webpack
学完webpack了,该学react了 get react
复制代码
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实现也比较简单
总结:SyncWaterfallHook主要仍是用于函数之间对结果存在依赖的场景
仍是前面的例子,若是一次学不懂一门技术,那就要多学几遍,例如:
const FrontEnd = new SyncLoopHook(['name']); let num = 0; FrontEnd.tap('webpack',(name)=>{ console.log(name+" get webpack ") return ++num === 3?undefined:'再学一次'; }); FrontEnd.tap('react',(name)=>{ console.log(name+" get react") }); FrontEnd.start=(...args)=>{ FrontEnd.call(...args) }; FrontEnd.start('xiaoming'); 复制代码
上面执行的结果是:
xiaoming get webpack
xiaoming get webpack
xiaoming get webpack
xiaoming get react
复制代码
总结:主要场景是同一任务,须要执行屡次
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++; } } } } 复制代码
也能够换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) }) } } 复制代码
总结:SyncLoopHook这个使用场景相对较少,不过了解一下也好
前面了解的都是同步hook,更关键的是异步hook
举个例子,同窗小王说去学前端了,但你也不知道他何时学完,只有他学完告诉你,你才知道他学完了,例:
const {AsyncParallelHook} = require('tapable'); const FrontEnd = new AsyncParallelHook(['name']); FrontEnd.tapAsync('webpack',(name,cb)=>{ setTimeout(() => { console.log(name+" get webpack ") cb(); }, 1000); }); FrontEnd.tapAsync('react',(name,cb)=>{ setTimeout(() => { console.log(name+" get react") cb(); }, 1000); }); FrontEnd.start=(...args)=>{ FrontEnd.callAsync(...args,()=>{ console.log("end"); }) }; FrontEnd.start('小王'); 复制代码
最终输出:
小王 get webpack
小王 get react
end
复制代码
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解决的问题和promise.all相似,都是用于解决异步并行的问题
前面虽然用:AsyncParralleHook可以解决异步,但并无使用primise,也没有类promise的概念
const {AsyncParallelHook} = require('tapable'); const FrontEnd = new AsyncParallelHook(['name']); FrontEnd.tapPromise('webpack',(name)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get webpack ") resolve(); }, 1000); }) }); FrontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") resolve(); }, 1000); }) }); FrontEnd.start=(...args)=>{ FrontEnd.promise(...args).then(()=>{ console.log("end"); }) }; FrontEnd.start('小王'); 复制代码
调用上面的api后,输出:
小王 get webpack
小王 get react
end
复制代码
总结:
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) } } 复制代码
AsyncParallelBailHook这个钩子和前面的钩子不太同样 按前面的例子来说:
这就是AsyncParallelBailHook处理的事情
const {AsyncParallelBailHook} = require('tapable'); const FrontEnd = new AsyncParallelBailHook(['name']); FrontEnd.tapPromise('webpack',(name)=>{ return new Promise((resolve,reject)=>{ setTimeout(() => { console.log(name+" get webpack ") reject('小王学崩了!'); }, 1000); }) }); FrontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") resolve(); }, 2000); }) }); FrontEnd.start=(...args)=>{ FrontEnd.promise(...args).then(()=>{ console.log("end"); },(err)=>{ console.log("据说:",err) }) }; FrontEnd.start('小王'); 复制代码
上面代码执行结果是:
小王 get webpack
据说: 小王学崩了!
小王 get react
复制代码
再看一个例子:
const {AsyncParallelBailHook} = require('tapable'); const FrontEnd = new AsyncParallelBailHook(['name']); FrontEnd.tapPromise('webpack',(name)=>{ return new Promise((resolve,reject)=>{ setTimeout(() => { console.log(name+" get webpack ") reject(); }, 1000); }) }); FrontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") resolve(); }, 2000); }) }); FrontEnd.start=(...args)=>{ FrontEnd.promise(...args).then(()=>{ console.log("end"); },(err)=>{ console.log("据说:",err) }) }; FrontEnd.start('小王'); 复制代码
和上面就改了1行,就是reject内容为空,此时输出:
小王 get webpack
小王 get react
end
复制代码
总结:
这个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) } } 复制代码
前面讲的是异步并行,如今该说异步串行了,例如小王,学完webpack才去学的react,你也不知道他何时学完,但他学完一个就会告诉你一下,例:
const {AsyncSeriesHook} = require('tapable'); const FrontEnd = new AsyncSeriesHook(['name']); console.time('webpack'); console.time('react'); FrontEnd.tapPromise('webpack',(name,cb)=>{ return new Promise((resolve,reject)=>{ setTimeout(() => { console.log(name+" get webpack ") console.timeEnd('webpack'); resolve(); }, 1000); }) }); FrontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") console.timeEnd('react'); resolve(); }, 1000); }) }); FrontEnd.start=(...args)=>{ FrontEnd.promise(...args).then(()=>{ console.log("end"); }) }; FrontEnd.start('小王'); 复制代码
上面代码执行结果:
小王 get webpack webpack: 1010.781ms 小王 get react react: 2016.598ms end 复制代码
总结:AsyncSeriesHook解决的问题是异步串行,例如node的os.cpus()有限,能够把任务分批次执行,这样对性能有保障
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)) } } 复制代码
仍是前面的例子,若是小王学前端,学了webapck就完全放弃了,那后面的react也就不用学了
const {AsyncSeriesBailHook} = require('tapable'); const FrontEnd = new AsyncSeriesBailHook(['name']); console.time('webpack'); console.time('react'); FrontEnd.tapPromise('webpack',(name,cb)=>{ return new Promise((resolve,reject)=>{ setTimeout(() => { console.log(name+" get webpack ") console.timeEnd('webpack'); reject('小王完全放弃了'); }, 1000); }) }); FrontEnd.tapPromise('react',(name,cb)=>{ return new Promise((resolve)=>{ setTimeout(() => { console.log(name+" get react ") console.timeEnd('react'); resolve(); }, 1000); }) }); FrontEnd.start=(...args)=>{ FrontEnd.promise(...args).then(()=>{ console.log("end"); }).catch((err)=>{ console.log("err",err) }) }; FrontEnd.start('小王'); 复制代码
上面代码输出:
小王 get webpack webpack: 1010.518ms err 小王完全放弃了 复制代码
场景:主要是异步串行,若是某一个任务执行的结果reject或者return,那么后面的都将再也不执行
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实现难度要高不少
SyncWaterFallHook前面已经了解过了,就是前一个执行完的结果会传递给下一个执行函数,和AsyncSeriesWaterfallHook的区别就是,一个是同步一个是异步
具体来讲,例如只有一本教材,小王学完,小张才能学
const FrontEnd = new AsyncSeriesWaterfallHook(['name']); FrontEnd.tapAsync('webpack',(name,cb)=>{ setTimeout(() => { console.log(name+" get webpack ") cb(null,'小李'); }, 1000); }); FrontEnd.tapAsync('webpack',(name,cb)=>{ setTimeout(() => { console.log(name+" get webpack ") cb(null,'小张'); }, 1000); }); FrontEnd.tapAsync('webpack',(name,cb)=>{ setTimeout(() => { console.log(name+" get webpack ") cb(null,'小红'); }, 1000); }); FrontEnd.start=(...args)=>{ FrontEnd.callAsync(...args,(data)=>{ console.log("全学完了",) }) }; FrontEnd.start('小王'); 复制代码
上面代码,最终输出:
小王 get webpack
小李 get webpack
小张 get webpack
全学完了
复制代码
总结:这个的用法和SyncWaterFallHook的用法一致
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(); } } 复制代码
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)) } } 复制代码