Some experiences in javascript exception handling
Write on the front
In order to improve the stability of the application, we have carried out the work of script exception management on the front-end project, conducted an overall troubleshooting on the js errors reported by production, and tried to improve the accuracy of related alarms by reducing the frequency of script exceptions. Combined with the relevant information read recently in this regard, we tried to make a summary periodically. Next, we will introduce some experience in js exception handling.
Let's start with the concept
What is an exception
Let's take a look at the official definition:
Error objects are thrown when runtime errors occur. The Error object can also be used as a base object for user-defined exceptions.
The description is very simple. Let's summarize that the code has encountered problems in the execution process. The program has been unable to run normally, and the Error object will be thrown. This is different from the exception object used in most programming languages. It is even more suitable to call it an error. It should be said that this is true. When the Error object is not thrown, it will not throw an exception if it is no different from other ordinary objects in js, At the same time, the Error object can also be used for user-defined error base objects.
The above red messages contain exception messages and stack traces, which play an important role in locating problems in the code. It can be seen that the stack trace is from the bottom file position 21:15 to the top file position 25:7; The first two consoles are not executed when encountering exceptions, and the code in the second script tag is executed normally.
Conclusion: When an unhandled exception occurs during the task execution, it will be thrown out along the call stack layer by layer (a little like an event bubble), which will eventually cause the current task to be terminated. After the current task terminates, the JS thread will continue to extract the next task from the task queue and continue to execute.
How to handle exceptions
Exceptions are inevitable, so in software development, reasonable exception handling becomes an indispensable part of high-quality code. Only by handling exceptions well can we effectively control the unexpected situations in the program. One of the most easy problems is to confuse exception handling with business processes.
According to the recommendations of Clean Code, we can follow the following principles to improve the code quality in the face of exceptions:
Prefer Exceptions to Returning Error Codes
Exceptions are preferred over error codes.
To understand this sentence, we still need to use examples. The first code below defines a Laptop class. In its sendShutDown method implementation, we use the if statement to check whether there is an invalid deviceID in the return value of getID. Error checking will make the caller's code complex and difficult to read business logic. At the same time, if this error check is omitted, it will also cause problems in the code, The error handling can be handed over to the language to make the whole process more elegant. The second code isolates two different logic for exception handling, which will bring some advantages:
1. The business process is clearer and easier to read. We understand exceptions and business processes as two different problems that can be handled separately;
2. The two separated logics are more focused and the code is more concise;
3. The responsibility of handling program exceptions is entrusted to the programming language, and the boundary is clarified;
Don't ignore caught error!
Do not ignore exception handling after catching exceptions!
In previous code reviews, we often saw that our classmates would not do anything in the catch block, or would write a console. log (error) due to the inspection of eslint, which also means that nothing was done. This is a dangerous way to deal with exceptions without taking any measures when they occur. Because these exceptions are usually caused by unexpected situations that we do not consider, and problems that are difficult to find in business logic can be found from them. Once we catch these exceptions, the top-level error monitoring cannot actively catch these problems. The program may not crash, but if the user does not tell us, We can't find out which functions of the user can't be used normally, so we should at least log these exceptions;
Don't ignore rejected promises!
Don't ignore the promise exception unless you are sure it has been handled!
We have learned a lesson in this area. In the AEM access project, we once disabled the script exception report_ unhandled_ When rejection is enabled, it is forbidden to capture all the promise exceptions. At that time, most of the promise exceptions in our online applications were uml request interface errors and ant form validation errors, which did not cause any online problems, so we naively believed that the uncaught promise exceptions were harmless; This idea is also dangerous, because after deep tracking, it is found that the interface request database has caught an exception and used message.error to process it. The form validation error exception is also the one that Antd chooses to continue to throw up after processing. These two are really harmless. However, when we face more unprocessed promise exceptions (for example, the interface returns success but the agreed data format is wrong), we do not report them at the same time, We lost the scene of many online problems, so we had to blindly guess and reproduce them, relying on user feedback.
Exceptions Hierarchy
Use custom exceptions to make the exception hierarchy clear.
It is very cool to manage exceptions in business code. The above chapter introduces some basic exception types provided by Javascript. These exception types are not related to our business. Therefore, it is not appropriate to use these exceptions to control the errors in the code. Our code is the modeling of our business. Similarly, we also need to model and manage these exceptions related to the business, semantize the exceptions, and trigger them when a specific situation occurs in the business logic. Otherwise, even if the caller catches an exception, he does not know how to handle it.
This often brings some benefits:
1. Using error instanceof CustomBizError makes it easier to identify exceptions, make the judgment logic more concise and read, and make it easier to handle captured exceptions and recover programs.
2. By standardizing our custom error classes, it is easier for us to do upper level processing. For example, I can choose not to report the interface exceptions mentioned above as script exceptions globally, because the information is usually reported in the interface exceptions;
Provide context with exceptions
Provide exception context
Once an exception occurs, there are usually exception messages, stack trace information, and file names to locate the site of the error. However, it is still difficult to locate even this way. Therefore, it is generally recommended to enrich the exception information so that we can locate the problem more quickly. It can explain our intentions where exceptions are caught. At the same time, these additional information should only be used by developers to locate problems. Users do not need to be aware of these exception contexts and not be reflected in the user interface.
Combined with the custom errors in the previous article, we also need to provide more rich contexts for these custom errors.
Suggestions in React
The JS error of the local UI should not cause the entire application to crash and blank screen. We should minimize its scope of influence, which is a conclusion easy to form consensus. Therefore, React 16 introduces the concept of error boundaries.
The official document [2] of React Error Boundaries mentions:
The error boundary is a React component that can capture JavaScript errors that occur anywhere in its subcomponent tree, print these errors, and display the degraded UI without rendering the crashed subcomponent tree. Error boundaries can catch errors that occur during rendering of the entire subcomponent tree, in lifecycle methods, and in constructors.
Many components of ProComponents [3] should use Error Boundaries, such as ProTable, to affect only the local UI when an exception occurs. The source code in @ ant design/pro utils is the same as the processing on the official website. For more information, see the official website for a very detailed introduction:
Therefore, the enlightenment to us is that some things at the block level in the component library or business system (the c bit in the spm model) must take component level exception handling into account.
Global reporting of exceptions
Basically, this is the ultimate solution to deal with unpredictable exceptions. It automatically collects error reports and gives an alarm when the threshold is reached. In an ideal situation, after an exception occurs, R&D students can find and locate the problem at the first time. They will mainly use two global events:
Window.onerror event
Most exceptions (including syntax errors) in the running of JS will trigger the error event on the window to execute the registered function. Unlike try catch, one error can sense both synchronous exceptions and asynchronous task exceptions (except promise exceptions). The use method is as follows.
In order to improve the stability of the application, we have carried out the work of script exception management on the front-end project, conducted an overall troubleshooting on the js errors reported by production, and tried to improve the accuracy of related alarms by reducing the frequency of script exceptions. Combined with the relevant information read recently in this regard, we tried to make a summary periodically. Next, we will introduce some experience in js exception handling.
Let's start with the concept
What is an exception
Let's take a look at the official definition:
Error objects are thrown when runtime errors occur. The Error object can also be used as a base object for user-defined exceptions.
The description is very simple. Let's summarize that the code has encountered problems in the execution process. The program has been unable to run normally, and the Error object will be thrown. This is different from the exception object used in most programming languages. It is even more suitable to call it an error. It should be said that this is true. When the Error object is not thrown, it will not throw an exception if it is no different from other ordinary objects in js, At the same time, the Error object can also be used for user-defined error base objects.
The above red messages contain exception messages and stack traces, which play an important role in locating problems in the code. It can be seen that the stack trace is from the bottom file position 21:15 to the top file position 25:7; The first two consoles are not executed when encountering exceptions, and the code in the second script tag is executed normally.
Conclusion: When an unhandled exception occurs during the task execution, it will be thrown out along the call stack layer by layer (a little like an event bubble), which will eventually cause the current task to be terminated. After the current task terminates, the JS thread will continue to extract the next task from the task queue and continue to execute.
How to handle exceptions
Exceptions are inevitable, so in software development, reasonable exception handling becomes an indispensable part of high-quality code. Only by handling exceptions well can we effectively control the unexpected situations in the program. One of the most easy problems is to confuse exception handling with business processes.
According to the recommendations of Clean Code, we can follow the following principles to improve the code quality in the face of exceptions:
Prefer Exceptions to Returning Error Codes
Exceptions are preferred over error codes.
To understand this sentence, we still need to use examples. The first code below defines a Laptop class. In its sendShutDown method implementation, we use the if statement to check whether there is an invalid deviceID in the return value of getID. Error checking will make the caller's code complex and difficult to read business logic. At the same time, if this error check is omitted, it will also cause problems in the code, The error handling can be handed over to the language to make the whole process more elegant. The second code isolates two different logic for exception handling, which will bring some advantages:
1. The business process is clearer and easier to read. We understand exceptions and business processes as two different problems that can be handled separately;
2. The two separated logics are more focused and the code is more concise;
3. The responsibility of handling program exceptions is entrusted to the programming language, and the boundary is clarified;
Don't ignore caught error!
Do not ignore exception handling after catching exceptions!
In previous code reviews, we often saw that our classmates would not do anything in the catch block, or would write a console. log (error) due to the inspection of eslint, which also means that nothing was done. This is a dangerous way to deal with exceptions without taking any measures when they occur. Because these exceptions are usually caused by unexpected situations that we do not consider, and problems that are difficult to find in business logic can be found from them. Once we catch these exceptions, the top-level error monitoring cannot actively catch these problems. The program may not crash, but if the user does not tell us, We can't find out which functions of the user can't be used normally, so we should at least log these exceptions;
Don't ignore rejected promises!
Don't ignore the promise exception unless you are sure it has been handled!
We have learned a lesson in this area. In the AEM access project, we once disabled the script exception report_ unhandled_ When rejection is enabled, it is forbidden to capture all the promise exceptions. At that time, most of the promise exceptions in our online applications were uml request interface errors and ant form validation errors, which did not cause any online problems, so we naively believed that the uncaught promise exceptions were harmless; This idea is also dangerous, because after deep tracking, it is found that the interface request database has caught an exception and used message.error to process it. The form validation error exception is also the one that Antd chooses to continue to throw up after processing. These two are really harmless. However, when we face more unprocessed promise exceptions (for example, the interface returns success but the agreed data format is wrong), we do not report them at the same time, We lost the scene of many online problems, so we had to blindly guess and reproduce them, relying on user feedback.
Exceptions Hierarchy
Use custom exceptions to make the exception hierarchy clear.
It is very cool to manage exceptions in business code. The above chapter introduces some basic exception types provided by Javascript. These exception types are not related to our business. Therefore, it is not appropriate to use these exceptions to control the errors in the code. Our code is the modeling of our business. Similarly, we also need to model and manage these exceptions related to the business, semantize the exceptions, and trigger them when a specific situation occurs in the business logic. Otherwise, even if the caller catches an exception, he does not know how to handle it.
This often brings some benefits:
1. Using error instanceof CustomBizError makes it easier to identify exceptions, make the judgment logic more concise and read, and make it easier to handle captured exceptions and recover programs.
2. By standardizing our custom error classes, it is easier for us to do upper level processing. For example, I can choose not to report the interface exceptions mentioned above as script exceptions globally, because the information is usually reported in the interface exceptions;
Provide context with exceptions
Provide exception context
Once an exception occurs, there are usually exception messages, stack trace information, and file names to locate the site of the error. However, it is still difficult to locate even this way. Therefore, it is generally recommended to enrich the exception information so that we can locate the problem more quickly. It can explain our intentions where exceptions are caught. At the same time, these additional information should only be used by developers to locate problems. Users do not need to be aware of these exception contexts and not be reflected in the user interface.
Combined with the custom errors in the previous article, we also need to provide more rich contexts for these custom errors.
Suggestions in React
The JS error of the local UI should not cause the entire application to crash and blank screen. We should minimize its scope of influence, which is a conclusion easy to form consensus. Therefore, React 16 introduces the concept of error boundaries.
The official document [2] of React Error Boundaries mentions:
The error boundary is a React component that can capture JavaScript errors that occur anywhere in its subcomponent tree, print these errors, and display the degraded UI without rendering the crashed subcomponent tree. Error boundaries can catch errors that occur during rendering of the entire subcomponent tree, in lifecycle methods, and in constructors.
Many components of ProComponents [3] should use Error Boundaries, such as ProTable, to affect only the local UI when an exception occurs. The source code in @ ant design/pro utils is the same as the processing on the official website. For more information, see the official website for a very detailed introduction:
Therefore, the enlightenment to us is that some things at the block level in the component library or business system (the c bit in the spm model) must take component level exception handling into account.
Global reporting of exceptions
Basically, this is the ultimate solution to deal with unpredictable exceptions. It automatically collects error reports and gives an alarm when the threshold is reached. In an ideal situation, after an exception occurs, R&D students can find and locate the problem at the first time. They will mainly use two global events:
Window.onerror event
Most exceptions (including syntax errors) in the running of JS will trigger the error event on the window to execute the registered function. Unlike try catch, one error can sense both synchronous exceptions and asynchronous task exceptions (except promise exceptions). The use method is as follows.
Related Articles
-
A detailed explanation of Hadoop core architecture HDFS
Knowledge Base Team
-
What Does IOT Mean
Knowledge Base Team
-
6 Optional Technologies for Data Storage
Knowledge Base Team
-
What Is Blockchain Technology
Knowledge Base Team
Explore More Special Offers
-
Short Message Service(SMS) & Mail Service
50,000 email package starts as low as USD 1.99, 120 short messages start at only USD 1.00