Tapable heard about it for a long time and finally decided to learn it systematically

Q1: Problem solved by Tapable?

  1. Tapable is a standalone library
  2. This library is used extensively in Webpack
  3. 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

  1. SyncHook currently looks more like a subscription publishing
  2. 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
  1. Limit is used for parameter verification
  2. Tasks are used to collect subscriptions
  3. The tap method is used to add methods to tasks
  4. 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:

  1. The main problem SyncBailHook addresses is conditional blocking
  2. When a subscribed event meets a judgment, the following process is no longer executed
  3. 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
  1. SyncWaterfallHook passes the execution results of the first task to the second
  2. The main usage scenario is dealing with interdependencies between logic
  3. 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

  1. Redux’s compose implementation will do
  2. The first step is to fetch the first execution and get the result RET
  3. The second step is to pass in the result RET as the reduce parameter
  4. 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
  1. The SyncLoopHook task can be executed multiple times
  2. 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
  1. The above implementation is done by counting
  2. Index does not move if the result is not undefined
  3. 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
  1. This implementation has no concept of subscripts
  2. 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
  1. AsyncParralleHook is an asynchronous parallel hook
  2. Usage scenarios, such as making simultaneous requests to two interfaces
  3. Note: This registration event is no longer TAP, but tapAsync
  4. Note: this time, instead of a call, the event is called callAsync
  5. You can see the distinction between synchronous and asynchronous subscriptions and publishing in Tapable
  6. 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
  1. The simplest way to AsyncParallelHook is by counting
  2. Add a counter to the instance
  3. 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
  1. Note: The method for binding the event at this point is called tapPromise
  2. Note that the method that executes the event at this point is called a promise

Conclusion:

  1. Tapable has three event binding methods: TAP, tapAsync, and tapPromise
  2. 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
  1. The core is to implement two methods, tapPromise and promise
  2. TapPromise isn’t really that different from the previous TAP (simple implementation problem)
  3. Promise: Return a promise. All

Q15: what is AsyncParallelBailHook?

AsyncParallelBailHook: AsyncParallelBailHook: AsyncParallelBailHook: AsyncParallelBailHook

  1. 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
  2. Xiao Wang learned webpack and broke down, so I told you
  3. 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
  4. But Xiao Wang also learned react but finished it
  5. 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
  1. In the example above, the first parallel task returns REJECT
  2. Reject, which is not undefined, goes directly to the promise.all catch
  3. 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
  1. A reject call does not enter a catch
  2. Reject returns empty, and subsequent tasks are executed as usual

Conclusion:

  1. AsyncParallelBailHook, if it returns true, goes straight to catch
  2. All tasks are executed no matter what the result is, right
  3. 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.
  4. If used to handle synchronization, it works the same as SyncBailHook
  5. If tapSync is processed, a return true is encountered and the final callback is not executed
  6. 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
  1. Normally, any task in promise.all, reject, enters a unified catch
  2. But what we need is a reject value for a catch
  3. So we wrapped another layer of promise in the original task
  4. If reject is true, reject is executed
  5. 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
  1. Two asynchronous tasks become serial
  2. 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
  1. The core implementation is the Promise serial
  2. 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
  1. The above code only executes to Webpack
  2. 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

  1. First, package a promise layer outside reduce
  2. 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
  3. At the same time, a post-then is added after all promises to check whether all promises are completed
  4. 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
  1. This is done primarily by encapsulating a callback function called next
  2. 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
  1. Promise implementation is relatively simple
  2. 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

  1. Each AsyncHook of Tapable supports TAP, tapAsync, and tapPromise at the same time
  2. Tapable mainly solves the problem of event flow, and each Hook uses different scenarios
  3. 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