purpose

Before we start learning about chains of responsibility, let’s take a look at some of the common problems in development. Here’s the front-end code for handling API error codes:

const httpErrorHandler = (error) => { const errorStatus = error.response.status; If (errorStatus === 400) {console.log(' Are you submitting something strange? '); } if (errorStatus === 401) {console.log(' need to log in first! '); } if (errorStatus === 403) {console.log(' '); } if (errorStatus === 404) {console.log(' There is nothing here... '); }};

Of course, you can’t have just one line of Console in a real project, but this is a simplified version of the principle.

The HttperRorHandler in the code will receive the API response error and do different things with the error status code, so there is a lot of if (or switch) in the code to determine what needs to be executed at the moment, and when you add code to handle new errors, I’m going to have to go to HttperRorHandler and change the code.

While it is inevitable to change the code frequently, doing so can lead to several problems, which are illustrated below in terms of Solid’s Single Responsibility and Open/Close principles:

Single Responsibility

Simply put, a single responsibility is doing one thing. The previous HttperRorHandler method, in terms of usage, gives it the error object and lets it do the corresponding processing according to the error code. It may seem like it’s doing a single thing called “error handling,” but from an implementation standpoint, it’s writing all the different error handling logic into HttperRorHandler, which leads to having to read a lot of unrelated code when you just want to change the logic of 400.

The Open/Close Principle

The open and closed principle means that the core logic that has been written should not be changed any more, but at the same time, the original function should be expanded due to the increase of demand, that is, the open and expanded function, and the correct logic should be modified by closing. Going back to HttperRorHandler, if you need to add a 405 processing logic (to extend the new functionality), then you need to change the code in HttperRorHandler (change the logic that was originally correct), which can easily cause the code that was executed correctly to fail.

So what if there are so many flaws in HttperRorHandler?

To solve the problem

The separation of logic

Let’s make HttperRorHandler conform to a single rule. First break down each error processing logic into methods:

Const response400 = () => {console.log(' Did you submit something strange? '); }; Const response401 = () = bb0 {console.log(' We need to log in first! '); }; Const response403 = () => {console.log(' '); }; Const response404 = () => {console.log(' There is nothing here... '); }; const httpErrorHandler = (error) => { const errorStatus = error.response.status; if (errorStatus === 400) { response400(); } if (errorStatus === 401) { response401(); } if (errorStatus === 403) { response403(); } if (errorStatus === 404) { response404(); }};

Just breaking down the logic of each block into methods saves us from having to read a lot of code in HttperRorHandler to change the error handling of a status code.

Just separating the logic also makes HttperRorHandler open and closed, because separating the error-handling logic into methods encapsulates the code that has already been done. When you add the error handling logic to HttperRorHandler for 405, you create a new response405 method instead of the other error handling logic methods (enclosing modifications). And add a new condition to HttperRorHandler (open extension new functionality).

The current HttperRorHandler is a strategy pattern that uses a uniform interface (method) to handle various error states. The difference between a policy pattern and a chain of responsibilities will be explained again at the end of this article.

Chain of Responsibility Pattern

For example, response400 will only execute if the status code is 400, and response401 will only handle errors in 401. The other methods are executed only when they should be. Everyone has a role to play, which is the chain of responsibility.

Next, start implementing.

Increase the judgment

By definition, each method has to know whether the current event is its own responsibility, so the if judgment that was implemented in HttperRorHandler is distributed to each method and is internally controlled:

const response400 = (error) => { if (error.response.status ! == 400) return; Console. log(' Are you submitting something strange? '); }; const response401 = (error) => { if (error.response.status ! == 401) return; Console. log(' Need to log in first! '); }; const response403 = (error) => { if (error.response.status ! == 403) return; Console. log(' Trying to steal something bad? '); }; const response404 = (error) => { if (error.response.status ! == 404) return; Console. log(' There is nothing here... '); }; const httpErrorHandler = (error) => { response400(error); response401(error); response403(error); response404(error); };

The HttperRorHandler code is much simpler and all the logic in HttperRorHandler is removed by putting the judgment logic into its own methods. Now the HttperRorHandler is just going to execute response400 through response404 in order, so it’s going to do what it needs to do, and it’s just going to do what it shouldn’t do.

Implement a true chain of responsibility

As soon as you refactor to the last step, all the split error handlers will decide for themselves whether or not they should be doing it, but if your code is like this, then someone else will look at HttperRorHandler in the future and say:

What kind of fairy code is this? The API performs all error handling as soon as it encounters an error?

Because they don’t know that there’s a judgment in each of these methods, and maybe after a while you’ll forget it yourself, because right now HttperRorHandler looks like it’s only going from response400 to response404, even though we know it’s working correctly, But you don’t see the chain of responsibility at all.

So how exactly does it look like a chain? Instead, you can simply write down all the error handling methods to be executed in a single number, and tell anyone who sees the code in the future that this is the chain of responsibility:

const httpErrorHandler = (error) => {
  const errorHandlerChain = [
    response400,
    response401,
    response403,
    response404
  ];
  errorHandlerChain.forEach((errorHandler) => {
    errorHandler(error);
  });
};

Optimize execution

In this case, the purpose of the chain of responsibility is achieved. If we use forEach as in the above code, then we don’t actually need to execute response401 to response404 when we get 400 errors.

So even in each error handling method combined with some logic, let each method can be judged, if meet their processing of things, just throw out a specified string or Boolean, then carry out after receiving the next method, but if this method can handle, after processed directly to the end, no longer need to continue to run the whole chain.

const response400 = (error) => { if (error.response.status ! == 400) return 'next'; Console. log(' Are you submitting something strange? '); }; const response401 = (error) => { if (error.response.status ! == 401) return 'next'; Console. log(' Need to log in first! '); }; const response403 = (error) => { if (error.response.status ! == 403) return 'next';; Console. log(' Trying to steal something bad? '); }; const response404 = (error) => { if (error.response.status ! == 404) return 'next';; Console. log(' There is nothing here... '); };

If the result of execution on a node in the chain is next, let the following method continue processing:

const httpErrorHandler = (error) => { const errorHandlerChain = [ response400, response401, response403, response404 ]; for(errorHandler of errorHandlerChain) { const result = errorHandler(error); if (result ! == 'next') break; }; };

Encapsulate the implementation of the chain of responsibility

Now the chain of responsibility has been implemented, but the logic to determine whether to give the next method (result! As a result, the implementation methods of each chain in the project may be different. Other chains may judge NextFollow-up or Boolean. In the end, it is necessary to package the implementations of the responsibility chain so that everyone in the team can use and follow the specifications in the project.

Chain of Responsibility requires:

  1. The current executor.
  2. The next recipient.
  3. Determine whether the current executor needs to be handed over to the next executor.

So the class should look like this:

class Chain { constructor(handler) { this.handler = handler; this.successor = null; } setSuccessor(successor) { this.successor = successor; return this; } passRequest(... args) { const result = this.handler(... args); if (result === 'next') { return this.successor && this.successor.passRequest(... args); } return result; }}

When we create objects with Chain, we need to pass in and set the current responsibility methods to Handler, and we can specify the next object in the Chain to a new object using setSuccessor. In setSuccessor, it returns this which is the whole Chain. So when we do the operations, we can just use setSuccessor to set the next receiver.

Finally, every object generated through the Chain will have a PassRequest to execute the current responsibility method… If the result returned is “next”, then the handler is required to execute the sucessor. If the result is “next”, then the handler is required to execute the sucessor. If the result is “next”, then the handler is required to execute the sucessor. If result is not next, result is returned directly.

With Chain the code will look like:

const httpErrorHandler = (error) => {
  const chainRequest400 = new Chain(response400);
  const chainRequest401 = new Chain(response401);
  const chainRequest403 = new Chain(response403);
  const chainRequest404 = new Chain(response404);

  chainRequest400.setSuccessor(chainRequest401);
  chainRequest401.setSuccessor(chainRequest403);
  chainRequest403.setSuccessor(chainRequest404);

  chainRequest400.passRequest(error);
};

At this point there is a lot of chain, you can continue to adapt to your own needs, or you don’t have to use classes, because the use of a design pattern doesn’t have to be limited to how it is implemented, as long as the intention of the pattern is expressed.

Advantages and disadvantages of chain of responsibility

Advantages:

  1. Compliance with a single responsibility so that there is only one responsibility in each method.
  2. In line with the open and closed principle, it is convenient to expand new responsibilities when the demand increases.
  3. Use time do not need to know who is the real treatment method, reduce a lot ofifswitchSyntax.

Disadvantages:

  1. Team members need to have a common understanding of the chain of responsibility, otherwise it will be strange to see a method return a Next for no apparent reason.
  2. When making mistakes, it is not easy to troubleshoot problems, because we do not know in which responsibility the mistake is made, and we need to look for it from the beginning of the chain.
  3. Even methods that do not require any processing will be executed because they are in the same chain. The examples in this article are all executed synchronously, which may take longer to execute if there is an asynchronous request.

Different from the policy pattern

I mentioned the policy pattern earlier, but the similarity between the two patterns is that you can define an interface (HttperRorHandler) for multiple of the same actions (response400, response401, etc.) without knowing who executed it. The policy pattern is relatively simple to implement.

Since the policy mode directly uses if or switch to control who should do this, it is better suited to a one-size-fits-all situation. Although the strategy mode does its own thing according to the wrong status code in the example, and both directly hand over the matter to the next bit when they are not in charge of themselves, each node in the responsibility chain can still do something first when they are not in charge of themselves, and then hand it over to the next node:

const response400 = (error) => { if (error.response.status ! == 400) {// Do something first... return 'next'; } console.log(' Are you submitting something strange? '); };

What kind of scenario would you use it in? For example, one day, you feel the world so big, should go to see, need to get a signature process: when leaving yourself, your Leader and hr need to do to sign it, so the chain of responsibility can the signing process of these three roles strung into a process, everyone to sign after a below, to the human resources after the sign to finish the whole process. And if the process is handled through a chain of responsibility, there is a way to be flexible no matter how the process changes or increases later.

The requirements in the above example are beyond the reach of the policy pattern.


This article first send WeChat messages public number: front-end pioneer

Welcome to scan the two-dimensional code to pay attention to the public number, every day to push you fresh front-end technology articles


Read on for the other great articles in this column:

  • Deep understanding of Shadow DOM V1
  • Step-by-step tutorial on implementing virtual reality games with WebVR
  • 13 modern CSS frameworks to help you improve your development efficiency
  • Quickly start BootstrapVue
  • How does a JavaScript engine work? Everything you need to know, from the call stack to the Promise
  • WebSocket in action: real-time communication between Node and React
  • 20 interview questions about Git
  • Deep parsing of Node.js console.log
  • What exactly is Node.js?
  • Build an API server with Node.js in 30 minutes
  • A copy of the JavaScript object
  • Programmer 30 years old before the monthly salary is less than 30K, which way to go
  • 14 of the best JavaScript data visualization libraries
  • 8 top VS Code extensions for the front end
  • A complete guide to Node.js multithreading
  • Convert HTML to PDF 4 solutions and implementation

  • More articles…