Tapable heard about it for a long time and finally decided to learn it systematically
Q1: Problem solved by Tapable?
- Tapable is a standalone library
- This library is used extensively in Webpack
- Tapable is designed to process events, solving problems similar to EventEmitter, but more powerful
Q2: What are tapable methods?
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
Copy the code
Ok, the method is so much, the first sight of the past, meng forced you and ME under the tree, so we still come a little bit, one by one analysis, learning and understanding
Q3: What is a SyncHook?
Let’s start with an example. What skills do front-end developers need to know?
Step1: first of all, we should make clear that the group is the front-end development
const {SyncHook}= require('tapable');
const FrontEnd = new SyncHook();
Copy the code
Ok, so those two sentences, we created a FrontEnd FrontEnd development
Step2: What skills do front-end development need to master, such as Webpack and React
FrontEnd.tap('webpack', () = > {console.log("get webpack")}); FrontEnd.tap('react', () = > {console.log("get react")});Copy the code
Ok, the tap above is used to bind events, adding two skills to front-end development
Step3: Skills need to be learned to master, so we have to learn the action
FrontEnd.learn=(a)= >{
FrontEnd.call()
};
FrontEnd.learn();
Copy the code
Step4: view the execution result
get webpack
get react
Copy the code
As you can see, through the above calls, our front-end development has learned react, Webpack
Step5: ginseng
FrontEnd needs to learn React and Webpack, but which developer has mastered these skills?
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');
Copy the code
Modify the previous code to add parameters, expected to output XXX get react
Step6: view the output result
undefined get webpack
undefined get react
Copy the code
The final result is undefined, which means the parameter is not passed in
Step7: add convention parameters to SyncHook
This is because const FrontEnd = new SyncHook(); When creating SyncHook, there are no agreed parameters, just add parameters as follows:
const {SyncHook}= require('tapable');
const FrontEnd = new SyncHook(['name']);// Add parameter conventions
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');
Copy the code
Final output:
xiaoming get webpack
xiaoming get react
Copy the code
SyncHook summary
- SyncHook currently looks more like a subscription publishing
- Just like add and fire in jquery, except tap and Call
Q4: How is SyncHook implemented?
SyncHook’s implementation is simple, and it’s the simplest subscription publishing
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));
}
}
Copy the code
- Limit is used for parameter verification
- Tasks are used to collect subscriptions
- The tap method is used to add methods to tasks
- The call method validates the parameters before executing all subscribed methods
Summary: The principle is relatively simple, not too much technical content, mainly a synchronous hook function
Q5: What is SyncBailHook?
If the previous event returns true, the next event is not executed, as in the previous example:
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');
Copy the code
At this point, changing the function from SyncHook to SyncBailHook makes no difference
But, think about it, it’s easy to get stuck learning, so modify our example:
const {SyncBailHook} =require('tapable');
const FrontEnd = new SyncBailHook(['name']);
FrontEnd.tap('webpack',(name)=>{
console.log(name+" get webpack ")
return 'I can't learn! ';
});
FrontEnd.tap('react',(name)=>{
console.log(name+" get react")}); FrontEnd.start=(. args) = >{ FrontEnd.call(... args) }; FrontEnd.start('xiaoming');
Copy the code
Only output:
xiaoming get webpack
Copy the code
React is not executed
Conclusion:
- The main problem SyncBailHook addresses is conditional blocking
- When a subscribed event meets a judgment, the following process is no longer executed
- Application scenarios: Scenarios a, A + B, A + B + C, and A + B + C + D
Q6: How is SyncBailHook implemented?
SyncBailHook is as simple as the previous example:
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));// Only one line is changed}}Copy the code
As you can see, SyncHook is very similar to SyncHook, except that the execution function forEach is replaced by some, because some is blocking and does not execute when it returns true
Q7: What is SyncWaterfullHook?
React: WebPack: WebPack: react: WebPack: WebPack: React
const {SyncWaterfallHook} = require('tapable');
const FrontEnd = new SyncWaterfallHook(['name']);
FrontEnd.tap('webpack',(name)=>{
console.log(name+" get webpack ")
return 'Webpack done, react time';
});
FrontEnd.tap('react',(name)=>{
console.log(name+" get react")}); FrontEnd.start=(. args) = >{ FrontEnd.call(... args) }; FrontEnd.start('xiaoming');
Copy the code
Output:
React get reactCopy the code
- SyncWaterfallHook passes the execution results of the first task to the second
- The main usage scenario is dealing with interdependencies between logic
- The effect is essentially the same as for the compose method in Redux
Q8: How to implement 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;
constret = first(... param); others.reduce((pre,next) = >{
return next(pre);
},ret)
}
}
Copy the code
The SyncWaterfallHook implementation is also relatively simple
- Redux’s compose implementation will do
- The first step is to fetch the first execution and get the result RET
- The second step is to pass in the result RET as the reduce parameter
- The third step is to iterate, passing arguments to the next function
Summary: SyncWaterfallHook is mainly used in scenarios where functions depend on results
Q9: What is a SyncLoopHook?
If you can’t learn a skill at once, you should learn it several times.
const FrontEnd = new SyncLoopHook(['name']);
let num = 0;
FrontEnd.tap('webpack',(name)=>{
console.log(name+" get webpack ")
return ++num === 3?undefined:'Learn it again';
});
FrontEnd.tap('react',(name)=>{
console.log(name+" get react")}); FrontEnd.start=(. args) = >{ FrontEnd.call(... args) }; FrontEnd.start('xiaoming');
Copy the code
The result of the above execution is:
xiaoming get webpack
xiaoming get webpack
xiaoming get webpack
xiaoming get react
Copy the code
- The SyncLoopHook task can be executed multiple times
- If undefined is returned, the execution stops, and if non-undefined is returned, the current task continues
Summary: The main scenario is the same task that needs to be performed multiple times
Q10: How is SyncLoopHook implemented?
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++; }}}}Copy the code
- The above implementation is done by counting
- Index does not move if the result is not undefined
- If the result is undefined the index is increased
You can also do doWhile instead
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)}}}Copy the code
- This implementation has no concept of subscripts
- The task group is traversed directly. If the execution result of a task in the task group is not undefined, the task is executed again
Summary: SyncLoopHook is a relatively rare use case, but it’s good to know
Q11: What is AsyncParralleHook?
The previous understanding is synchronous hook, the more critical is asynchronous hook
For example, my classmate Xiao Wang said he had gone to learn English, but I don’t know when he finished his study. Only when he finished his study and told me, did I know he had finished his study.
const {AsyncParallelHook} = require('tapable');
const FrontEnd = new AsyncParallelHook(['name']);
FrontEnd.tapAsync('webpack',(name,cb)=>{
setTimeout((a)= > {
console.log(name+" get webpack ")
cb();
}, 1000);
});
FrontEnd.tapAsync('react',(name,cb)=>{
setTimeout((a)= > {
console.log(name+" get react")
cb();
}, 1000);
});
FrontEnd.start=(. args) = >{ FrontEnd.callAsync(... args,()=>{console.log("end"); })}; FrontEnd.start('wang');
Copy the code
Final output:
Get webpack Get react endCopy the code
- AsyncParralleHook is an asynchronous parallel hook
- Usage scenarios, such as making simultaneous requests to two interfaces
- Note: This registration event is no longer TAP, but tapAsync
- Note: this time, instead of a call, the event is called callAsync
- You can see the distinction between synchronous and asynchronous subscriptions and publishing in Tapable
- Note: To receive notifications after all asynchronous execution is complete, you need to execute cb()
Q12: How to implement 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=(a)= >{
index++;
if(index === this.tasks.length){ finalCallBack(); }}this.tasks.forEach(item= >item(... param,done)) } }Copy the code
- The simplest way to AsyncParallelHook is by counting
- Add a counter to the instance
- Tasks are then iterated, and finalCallBack is executed when the number of successful tasks equals the total number of tasks
Summary: AsyncParallelHook solves a similar problem to promise.all, which solves the problem of asynchronous parallelism
Q13:AsyncParralleHook (2) How to use promise?
Although AsyncParralleHook can be used to solve asynism, primise is not used and there is no concept of class promise
const {AsyncParallelHook} = require('tapable');
const FrontEnd = new AsyncParallelHook(['name']);
FrontEnd.tapPromise('webpack',(name)=>{
return new Promise((resolve) = >{
setTimeout((a)= > {
console.log(name+" get webpack ")
resolve();
}, 1000); })}); FrontEnd.tapPromise('react',(name,cb)=>{
return new Promise((resolve) = >{
setTimeout((a)= > {
console.log(name+" get react ")
resolve();
}, 1000); })}); FrontEnd.start=(. args) = >{ FrontEnd.promise(... args).then((a)= >{
console.log("end"); })}; FrontEnd.start('wang');
Copy the code
After calling the above API, the output is:
Get webpack Get react endCopy the code
- Note: The method for binding the event at this point is called tapPromise
- Note that the method that executes the event at this point is called a promise
Conclusion:
- Tapable has three event binding methods: TAP, tapAsync, and tapPromise
- Tapable has three event execution methods: Call, callAsync, and 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)
}
}
Copy the code
- The core is to implement two methods, tapPromise and promise
- TapPromise isn’t really that different from the previous TAP (simple implementation problem)
- Promise: Return a promise. All
Q15: what is AsyncParallelBailHook?
AsyncParallelBailHook: AsyncParallelBailHook: AsyncParallelBailHook: AsyncParallelBailHook
- Classmate Wang said to learn the front end, but you do not know when he finished learning, only he finished telling you, you know he finished learning
- Xiao Wang learned webpack and broke down, so I told you
- When you heard that Xiao Wang’s school collapsed, you thought he couldn’t go on studying. So you told the big guys that Xiao Wang’s school collapsed
- But Xiao Wang also learned react but finished it
- Although finished learning, but you have announced the death of Xiao Wang, very hit in the face, so don’t know
That’s what AsyncParallelBailHook does
const {AsyncParallelBailHook} = require('tapable');
const FrontEnd = new AsyncParallelBailHook(['name']);
FrontEnd.tapPromise('webpack',(name)=>{
return new Promise((resolve,reject) = >{
setTimeout((a)= > {
console.log(name+" get webpack ")
reject('Xiao Wang has collapsed! ');
}, 1000); })}); FrontEnd.tapPromise('react',(name,cb)=>{
return new Promise((resolve) = >{
setTimeout((a)= > {
console.log(name+" get react ")
resolve();
}, 2000); })}); FrontEnd.start=(. args) = >{ FrontEnd.promise(... args).then((a)= >{
console.log("end");
},(err)=>{
console.log("Heard:",err)
})
};
FrontEnd.start('wang');
Copy the code
The result of the above code execution is:
I heard that Xiao Wang's school has collapsed. Wang get the reactCopy the code
- In the example above, the first parallel task returns REJECT
- Reject, which is not undefined, goes directly to the promise.all catch
- React will still execute asynchronous tasks, but will not be processed after success
Here’s another example:
const {AsyncParallelBailHook} = require('tapable');
const FrontEnd = new AsyncParallelBailHook(['name']);
FrontEnd.tapPromise('webpack',(name)=>{
return new Promise((resolve,reject) = >{
setTimeout((a)= > {
console.log(name+" get webpack ")
reject();
}, 1000); })}); FrontEnd.tapPromise('react',(name,cb)=>{
return new Promise((resolve) = >{
setTimeout((a)= > {
console.log(name+" get react ")
resolve();
}, 2000); })}); FrontEnd.start=(. args) = >{ FrontEnd.promise(... args).then((a)= >{
console.log("end");
},(err)=>{
console.log("Heard:",err)
})
};
FrontEnd.start('wang');
Copy the code
Reject (reject); reject (reject);
Get webpack Get react endCopy the code
- A reject call does not enter a catch
- Reject returns empty, and subsequent tasks are executed as usual
Conclusion:
- AsyncParallelBailHook, if it returns true, goes straight to catch
- All tasks are executed no matter what the result is, right
- The main scenario is that you request three interfaces in parallel, any one of them returns a result, and if it returns, you catch it.
- If used to handle synchronization, it works the same as SyncBailHook
- If tapSync is processed, a return true is encountered and the final callback is not executed
- If you deal with promises, you encounter rejcet(true) and go straight to catch
Q16: AsyncParallelBailHook how to do?
This AsyncParallelBailHook has been burning my brain for a while
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)
}
}
Copy the code
- Normally, any task in promise.all, reject, enters a unified catch
- But what we need is a reject value for a catch
- So we wrapped another layer of promise in the original task
- If reject is true, reject is executed
- If reject is false, then resolve as if nothing happened
Q17: AsyncSeriesHook is what?
React/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack/webpack
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((a)= > {
console.log(name+" get webpack ")
console.timeEnd('webpack');
resolve();
}, 1000); })}); FrontEnd.tapPromise('react',(name,cb)=>{
return new Promise((resolve) = >{
setTimeout((a)= > {
console.log(name+" get react ")
console.timeEnd('react');
resolve();
}, 1000); })}); FrontEnd.start=(. args) = >{ FrontEnd.promise(... args).then((a)= >{
console.log("end"); })}; FrontEnd.start('wang');
Copy the code
The result of the above code execution:
Wang get webpack webpack1010.781I don't know how to react.2016.598ms
end
Copy the code
- Two asynchronous tasks become serial
- From the time, it can be concluded that the total time of two asynchronous tasks of 1s becomes 2s after serial
Summary: AsyncSeriesHook solves the problem of asynchronous serials, for example, node os.cpus() is limited, and tasks can be executed in batches to ensure performance
Q18: How to implement 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((a)= >next(... param)) },first(... param)) } }Copy the code
- The core implementation is the Promise serial
- Take the first task, execute it, get the Promise instance, and iterate through Reduce
Q19: what is AsyncSeriesBailHook?
If Wang had learned webapck, he would have given up completely, and react would not have been needed
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((a)= > {
console.log(name+" get webpack ")
console.timeEnd('webpack');
reject('Xiao Wang has given up completely');
}, 1000); })}); FrontEnd.tapPromise('react',(name,cb)=>{
return new Promise((resolve) = >{
setTimeout((a)= > {
console.log(name+" get react ")
console.timeEnd('react');
resolve();
}, 1000); })}); FrontEnd.start=(. args) = >{ FrontEnd.promise(... args).then((a)= >{
console.log("end");
}).catch((err) = >{
console.log("err",err)
})
};
FrontEnd.start('wang');
Copy the code
Output from the above code:
Wang get webpack webpack1010.518Ms Err Wang gave up completelyCopy the code
- The above code only executes to Webpack
- AsyncSeriesBailHook, which blocks if the task returns, or reject
Scenario: Mainly asynchronous serialization. If a task executes a reject or return, the rest will not be executed
Q20: How to implement 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((a)= >next(... param)) .catch((err= >{
arr.splice(index,arr.length-index);
reject(err);
})).then((a)= >{
(index+1=== arr.length) && resolve(); }) },first(... param)) }) } }Copy the code
AsyncSeriesBailHook is much harder to implement
- First, package a promise layer outside reduce
- When any subtask enters catch, the fourth reduce parameter ARR will be cut so that it cannot proceed further, that is, the continuation of Reduce will be stopped
- At the same time, a post-then is added after all promises to check whether all promises are completed
- Why do we use index+1, because the post then is definitely the last task, but the index is still at the last index, so we just add 1
Q21: what is AsyncSeriesWaterfallHook?
SyncWaterFallHook (AsyncSeriesWaterfallHook) SyncWaterFallHook (AsyncSeriesWaterfallHook) SyncWaterFallHook (AsyncSeriesWaterfallHook
To be specific, for example, only one textbook, Xiao Wang finished learning, xiao Zhang can learn
const FrontEnd = new AsyncSeriesWaterfallHook(['name']);
FrontEnd.tapAsync('webpack',(name,cb)=>{
setTimeout((a)= > {
console.log(name+" get webpack ")
cb(null.'xiao li');
}, 1000);
});
FrontEnd.tapAsync('webpack',(name,cb)=>{
setTimeout((a)= > {
console.log(name+" get webpack ")
cb(null.'zhang');
}, 1000);
});
FrontEnd.tapAsync('webpack',(name,cb)=>{
setTimeout((a)= > {
console.log(name+" get webpack ")
cb(null.'little red');
}, 1000);
});
FrontEnd.start=(. args) = >{ FrontEnd.callAsync(... args,(data)=>{console.log("It's all over.",)}}; FrontEnd.start('wang');
Copy the code
In the code above, the final output is:
Xiao Wang got webpackCopy the code
Summary: This is the same as SyncWaterFallHook
Q22: How to implement 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(); }}Copy the code
- This is done primarily by encapsulating a callback function called next
- It then calls the tasks in the task queue over and over again, passing in the same callback function as the call
The implementation of prMISE version is as follows:
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) = >{
returndata? next(data):next(... param); }) },first(... param)) } }Copy the code
- Promise implementation is relatively simple
- The main thing is to see if there is anything in the then method, if so, pass the next function, if not, use the initial argument
conclusion
- Each AsyncHook of Tapable supports TAP, tapAsync, and tapPromise at the same time
- Tapable mainly solves the problem of event flow, and each Hook uses different scenarios
- Tapable is mainly applied in each life cycle of Webpack, and the specific practice needs to be combined with the principle of Webpack
reference
Tapable Usage Guide