purpose
Before starting to learn about the chain of responsibility, let's take a look at the common problems in development. The following is the code used by the front end to handle API error codes:
const httpErrorHandler = (error) => {
const errorStatus = error.response.status;
if (errorStatus === 400) {
console.log('你是不是提交了什么奇怪的东西?');
}
if (errorStatus === 401) {
console.log('需要先登陆!');
}
if (errorStatus === 403) {
console.log('是不是想偷摸干坏事?');
}
if (errorStatus === 404) {
console.log('这里什么也没有...');
}
};
Of course, there cannot be only one line of console
in the actual project. This is a simplified version to illustrate the principle.
httpErrorHandler
in the code will receive the API response error and handle the error status code differently, so the code needs a lot of if
(or switch
) to determine what needs to be executed currently, when you want to add processing code to a new error , You have to modify the code httpErrorHandler
Although it is inevitable to frequently modify the code, doing so may cause several problems. The following is based on SOLID's single responsibility and open/close principles:
Single responsibility
Simply put, a single responsibility is to only do one thing. httpErrorHandler
the perspective of use, the previous 060eb941b12d4e method is to hand over the error object to it, and let it do the corresponding processing according to the error code. It seems to be doing the single thing of "error handling", but from an implementation point of view, it writes all the different error handling logic in httpErrorHandler
, which may lead to the possibility of only wanting to modify the error code When the logic is 400, but I have to read a lot of irrelevant code.
Open and closed principle (open/close)
The principle of open and closed means that the core logic that has been written should not be changed, but at the same time, the original function should be expanded due to the increase in demand, which is to open the expansion function, and at the same time, the original correct logic should be closed and modified. Looking back at httpErrorHandler
, if you need to add a processing logic for error code 405 (to expand new functions), you need to modify httpErrorHandler
(modify the original correct logic), which can easily cause the original correct execution The code is wrong.
Since httpErrorHandler
so many flaws in 060eb941b12e04, what should we do?
Solve the problem
Separation logic
Let httpErrorHandler
conform to the single principle first. First, separate the processing logic of each error into methods:
const response400 = () => {
console.log('你是不是提交了什么奇怪的东西?');
};
const response401 = () => {
console.log('需要先登陆!');
};
const response403 = () => {
console.log('是不是想偷摸干坏事?');
};
const response404 = () => {
console.log('这里什么也没有...');
};
const httpErrorHandler = (error) => {
const errorStatus = error.response.status;
if (errorStatus === 400) {
response400();
}
if (errorStatus === 401) {
response401();
}
if (errorStatus === 403) {
response403();
}
if (errorStatus === 404) {
response404();
}
};
Although only the logic of each block is divided into methods, this already allows us to modify the error handling of a certain status code without reading a lot of code in httpErrorHandler
Just the operation of separating logic also makes httpErrorHandler
comply with the open and closed principle, because when the logic of error handling is split into methods, it is equivalent to encapsulating the completed code. At this time, when it needs to be httpErrorHandler
When the error handling logic for 405 is added, it will not affect other error handling logic methods (closed modification). Instead, create a new response405
method and add new condition judgments to httpErrorHandler
Expand new functions).
The current httpErrorHandler
is actually a strategy pattern. httpErrorHandler
uses a unified interface (method) to handle various error states. At the end of this article, the difference between the strategy pattern and the chain of responsibility will be explained again.
Chain of Responsibility Pattern
The realization principle of the chain of responsibility is very simple. It is to string all the methods together and execute them one by one, and each method can only do what it needs to do. For example, response400
only executed when the status code is 400, and response401
only Dealing with 401 errors, other methods are only executed when it is time to deal with them. Everyone performs their duties, which is the chain of responsibility.
Then start to achieve.
Increase judgment
According to the definition of the chain of responsibility, each method must be present to know it is not their own should be addressed, so should originally httpErrorHandler
implemented if
judge dispersed to each method, into their internal control responsibilities:
const response400 = (error) => {
if (error.response.status !== 400) return;
console.log('你是不是提交了什么奇怪的东西?');
};
const response401 = (error) => {
if (error.response.status !== 401) return;
console.log('需要先登陆!');
};
const response403 = (error) => {
if (error.response.status !== 403) return;
console.log('是不是想偷摸干坏事?');
};
const response404 = (error) => {
if (error.response.status !== 404) return;
console.log('这里什么也没有...');
};
const httpErrorHandler = (error) => {
response400(error);
response401(error);
response403(error);
response404(error);
};
After putting the judgment logic into the respective methods, httpErrorHandler
is simplified a lot, and all the logic httpErrorHandler
httpErrorHandler
. Now 060eb941b13c1f only needs to execute response400
to response404
in order. The execution is only return
directly.
Realize a true chain of responsibility
Although as long as you refactor to the previous step, all the split error handling methods will determine whether they should be done at present, but if your code is like this, then httpErrorHandler
in the future will only say:
What kind of fairy code is this? Does API perform all error handling as soon as it encounters an error?
Because they don’t know that there are judgments in each processing method, maybe you will forget about it after a while, because the current httpErrorHandler
seems to be only from response400
to response404
, even if we know the function is correct, but it is completely It doesn't come out that the chain of responsibility is used.
So how can it look like a chain? In fact, you can directly record all the error handling methods to be executed with a number, and tell people who will see this code in the future by naming it here is the chain of responsibility:
const httpErrorHandler = (error) => {
const errorHandlerChain = [
response400,
response401,
response403,
response404
];
errorHandlerChain.forEach((errorHandler) => {
errorHandler(error);
});
};
Optimized execution
In this way, the purpose of the chain of responsibility is achieved. If forEach
used in the above code, when a 400 error is encountered, there is actually no need to execute the following response401
to response404
.
Therefore, some logic should be added to each error handling method so that each method can judge. If it encounters something that cannot be handled by itself, throw out a specified string or boolean value, and then retry it after receiving it. Then execute the next method, but if the method can be processed, it ends directly after the processing is completed, and there is no need to continue to run the entire chain.
const response400 = (error) => {
if (error.response.status !== 400) return 'next';
console.log('你是不是提交了什么奇怪的东西?');
};
const response401 = (error) => {
if (error.response.status !== 401) return 'next';
console.log('需要先登陆!');
};
const response403 = (error) => {
if (error.response.status !== 403) return 'next';;
console.log('是不是想偷摸干坏事?');
};
const response404 = (error) => {
if (error.response.status !== 404) return 'next';;
console.log('这里什么都没有...');
};
If the execution result of 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 realization of the chain of responsibility
Now the responsibility chain has been implemented, but the logic of determining whether to give the next method (judgment result !== 'next'
) is exposed, which may cause the implementation method of each chain in the project to be different, and other chains may be Judge nextSuccessor
or boolean
, so finally you need to encapsulate the realization of the responsibility chain so that everyone in the team can use and comply with the specifications in the project.
The chain of responsibility requires:
- The current performer.
- The next recipient.
- Determine whether the current executor needs to be handed over to the next executor after execution.
So it should look like this after being encapsulated into a class:
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;
}
}
With Chain
needs to be passed in the current responsibilities of the method object is created and set to handler
, and can be used on the new object setSuccessor
assigned to the next object in the chain successor
, in setSuccessor
in return on behalf of the whole chain of this
, so When operating, you can use setSuccessor
set the next receiver setSuccessor
Finally, each object generated Chain
passRequest
to execute the current responsibility method. …arg
will turn all the incoming parameters into an array, and then hand it over to handler
which is the current responsibility method for execution. If the returned result is result
If it is next, it will determine whether sucessor
is specified. If so, continue execution. If result
is not next, it will directly return to result
.
With Chain
, the code will become:
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 time, it feels like a chain. You can continue to make adjustments according to your needs, or you don’t have to use classes, because the use of design patterns does not need to be limited to how to implement, as long as the pattern is expressed The intention is enough.
Pros and cons of the chain of responsibility
advantage:
- Conform to a single responsibility, so that there is only one responsibility in each method.
- It conforms to the open and closed principle, and can easily expand new responsibilities when demand increases.
- You don't need to know who is the real processing method when you use it, reducing a lot of
if
orswitch
syntax.
Disadvantages:
- Team members need to have a consensus on the chain of responsibility, otherwise it will be strange when a method returns a next inexplicably.
- It is not easy to troubleshoot the problem when it is wrong, because you don't know in which responsibility the error occurred, and you need to look back from the beginning of the chain.
- Even methods that do not require any processing will be executed because it is in the same chain. The examples in the article are executed synchronously. If there is an asynchronous request, the execution time may be longer.
Difference from strategy mode
I also mentioned the strategy mode earlier. Let me talk about the similarities between the two modes, that is, it is possible to define an interface ( httpErrorHandler
response400
, response401
etc.), and it is not used when it is used. Need to know who executed it in the end. The strategy model is relatively simple in implementation.
Because the strategy mode directly uses if
or switch
to control who should do this, it is more suitable for the situation of one carrot and one hole. Although the strategy mode in the example also does its own thing for the wrong status code, and directly hand over the matter to the next one when it is not in its own control, but each node in the chain of responsibility can still do its own thing. When you manage it yourself, do something first, and then hand it over to the next node:
const response400 = (error) => {
if (error.response.status !== 400) {
// 先做点什么...
return 'next';
}
console.log('你是不是提交了什么奇怪的东西?');
};
In what scenarios should it be used? For example, one day if you think the world is so big, you should check it out. When you leave your job, you need to go through a signing process: you, your leader, and your personnel all need to sign, so the responsibility chain can integrate these three roles The signing process of is strung together into a process, and after each person signs it, it will be handed over to the next person, and the whole process will not be completed until the human resources are signed. And if this process is handled through the chain of responsibility, no matter how the process changes or increases afterwards, there are ways to deal with it flexibly.
The requirements of the above example are beyond the strategic model.
This article first published WeChat public account: Front-end Pioneer
Welcome to scan the QR code to follow the official account, and push you fresh front-end technical articles every day
Welcome to continue reading other highly praised articles in this column:
- Deep understanding of Shadow DOM v1
- Step by step teaches you to use WebVR to realize virtual reality games
- 13 modern CSS frameworks to help you improve development efficiency
- Quickly get started with BootstrapVue
- JavaScript engine work? Everything you need to know from call stack to
- WebSocket combat: real-time communication between Node and React
- 20 interview questions about Git
- depth analysis of Node.js console.log
- What exactly is Node.js?
- Build an API server with Node.js in 30 minutes
- Javascript object copy
- Programmers have a monthly salary of less than 30K before the age of 30, so what should they do?
- 14 best JavaScript data visualization libraries
- 8 top VS Code extension plug-ins for the front end
- Node.js Multithreading Complete Guide
- 4 schemes and implementations of converting HTML to PDF
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。