内容简介:好的,方法一共是上述这么多,第一眼看过去,懵逼树下你和我,所以我们还是一点点来,一个个的分析、学习和了解先来个使用的例子,例如前端开发者需要掌握哪些技能?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方法中是否有内容,如果有的话,则传递个下一个函数,如果没有,则用初始参数
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Numerical Recipes 3rd Edition
William H. Press、Saul A. Teukolsky、William T. Vetterling、Brian P. Flannery / Cambridge University Press / 2007-9-6 / GBP 64.99
Do you want easy access to the latest methods in scientific computing? This greatly expanded third edition of Numerical Recipes has it, with wider coverage than ever before, many new, expanded and upd......一起来看看 《Numerical Recipes 3rd Edition》 这本书的介绍吧!