A, takeaway

The word “abnormal” comes from the Book of the Later Han dynasty, Volume i. Empress Ji shangguang Liyin Empress Ji, which means abnormal, different from ordinary. In our real life, there are also anomalies everywhere, such as street lights in small counties in disrepair… The subway in Shenzhen is always so crowded during rush hours… People get sick from time to time and so on; Thus, the world is full of mistakes, this is a basic fact.

In the computer world, abnormal refers to abnormal events occurred in the process of the program is run by some of the errors are caused by the external environment, some error is due to the developers as a result of negligence, effective processing these errors, ensure the normal operation of the computer is indispensable in our developers.

Second, the background

With the continuous expansion of the project and the continuous access of customers, the stability of the project has become a major challenge for the team.

When the user or team tester encounters a problem, it is most likely to directly throw the developer a blank screen or a screenshot of the wrong UI, and the error is not necessarily present, which makes the students at the front and back end feel headache about locating the problem. Is there a way to improve the user experience while helping developers quickly locate and solve problems?

In line with the business principle of “customer is King”, it is the responsibility of front-end developers to create a good user experience for users. When a page goes wrong, reminding the user of what is happening in a proper way at the right time is a much friendlier way to deal with it than when the page crashes or freezes.

The project must handle the following abnormal scenarios:

  • Grammar mistakes
  • Abnormal events
  • HTTP request exception
  • Static resource loading is abnormal
  • Abnormal Promise
  • The Iframe abnormal
  • Page collapse

The overall exception handling scheme needs to achieve two effects:

  1. Improve user experience
  2. Report to the monitoring system, which can find, locate and solve problems in time

Let’s start with a few exception scenarios and step by step explore how to resolve these exceptions and provide a better user experience.

Error type

Before getting into specific solutions, let’s take a look at the various types of front-end errors and familiarize ourselves with them.

Seven types of errors are defined by the ECMA-262 specification:

  • Error
  • EvalError
  • RangeError
  • ReferenceError
  • SyntaxError
  • TypeError
  • URIError

Error

Error is the base class for all errors, from which all other errors inherit

EvalError

The EvalError object represents an error that occurs in the global function eval(). If there is no error in eval(), it will not be thrown. You can create an instance of this object through the constructor

RangeError

The RangeError object represents an error when a value is not in the set or range of allowed values.

ReferenceError

This object represents an error when referring to a variable that does not exist:

SyntaxError

This exception is thrown when the JavaScript engine encounters markup or markup order that does not match the syntax of the language while parsing code:

TypeError

The operands passed to the function or the types that participate in the expectation of the operator or function are incompatible:

URIError

When the global URI handler is used in the wrong way:

Fourth, treatment and prevention

As mentioned above, errors and exceptions are ubiquitous and exist in a variety of application scenarios. How can we effectively intercept exceptions and nip errors in the bud so that users are not aware of them? Or do you downgrade when you encounter a fatal error?

(1) try catch

1. Grammar

Version 3 of ECMA-262 introduced try-catch as a standard way to handle exceptions in JavaScript. The basic syntax is shown below.

Try {// code that may cause error} catch (error) {// What to do if an error occurs}Copy the code

2. The motivation

Using a try… To catch an exception, I have two main motivations:

1) You really want to catch exceptions to code that could go wrong;

2) I want to make sure that the following code continues to run.

Motivation number one, there’s nothing to talk about, but here, let’s talk about motivation number two. Suppose we have the following code:

console.log(foo); //foo does not define console.log('I want running')Copy the code

As soon as the code executes, guess what? The first line is an error, and the second line’s log is not printed. If we change our code to something like this:

try{ 
    console.log(foo)
}catch(e){
    console.log(e)
}
console.log('I want running');
Copy the code

After the above code is executed, a ReferenceError is reported, but the subsequent log can be executed.

From this example, we can see that if the previous (synchronous) code has an exception that was not caught by the developer, the later code will not execute. So, if you want the block of code that is currently failing to work properly, then you need to use try… Catch to actively catch an exception.

Extension:

In fact, how bad code interferes with subsequent code execution is a topic worth exploring. The following is a specific discussion. Because there are so many browsers on the market, the implementation of the standard is not very consistent. So, the conclusion here is only based on Chromev91.0.4472.114. In the course of this discussion, we touched on two sets of concepts: synchronous and asynchronous code, and code writing and code running.

Scenario 1: Synchronize code (error) + Synchronize code

As you can see, the synchronization code following the failed synchronization code is not executed.

Scenario 2: Synchronous code (error) + asynchronous code

As in the previous case, asynchronous code is also affected and does not execute.

Scenario 3: Asynchronous code (error) + synchronous code

As you can see, errors in asynchronous code do not affect subsequent synchronous code execution.

Scenario 4: Asynchronous code (error) + asynchronous code

Bad asynchronous code does not affect subsequent asynchronous code execution.

If you just look at scenario 1, 2, and 3, it’s easy to conclude that synchronous code always executes before asynchronous code during code runtime. If the synchronization code executed first does not fail, then the following code will execute normally, otherwise the following code will not execute. But scene four breaks that conclusion. Let’s move on to scenario five.

Scenario 5: Asynchronous code + Synchronous code (error) + asynchronous code

See? It’s asynchronous code, and the logic is, if you’re affected by synchronous code that goes wrong, you’re either going to execute neither, or you’re going to execute both, right? Why should asynchronous code written before the writing date of the offending code execute normally, but not later? After verification, firefoxv75.0 is the same performance.

So, at this point, we can basically conclude that, at runtime, the question of how one of the two pieces of code that goes wrong affects the other to continue executing is not related to asynchronous code, it’s related to synchronous code; It doesn’t matter when the code is executed, it only matters when the code is written.

In human terms, asynchronous code can fail without affecting the execution of other code. An error in synchronous code, if written before other code at the time of writing and without manual exception catching, will affect the execution of other code (whether synchronous or asynchronous).

To sum up, if we want to ensure that the code following a piece of synchronous code that could go wrong continues to execute, we must do exception catching for that piece of synchronous code.

Scope of 3.

Only runtime errors from synchronous code can be caught, not syntactic errors and errors from asynchronous code.

When encountering a syntax error:

When encountering an asynchronous runtime error:

(2) Promise.catch()

1. Grammar

const promise1 = new Promise((resolve, reject) => { throw 'Uh-oh! '; }); promise1.catch((error) => { console.error(error); }); // expected output: Uh-oh!Copy the code

2. The motivation

Used to catch errors in promise code

Scope of 3.

Using promise.prototype.catch () to catch exceptions easily, let’s test common syntax errors, code errors, and asynchronous errors.

When encountering code errors, you can catch:

Syntax errors cannot be caught when encountered:

When an asynchronous runtime error is encountered, it cannot catch:

(3) unhandledrejection

Use 1.

Unhandledrejection: When a Promise is rejected and there is no Reject handler, the Unhandledrejection event is emitted

window.addEventListener("unhandledrejection", function(e){
  console.log(e);
});
Copy the code

2. The motivation

To prevent any missed Promise exceptions, we can add a global monitor on unhandledrejection for global monitoring of Uncaught Promise errors.

Scope of 3.

Window. addEventListener("unhandledrejection", function (e) {console.log(" catch promise exception :", e); e.preventDefault(); }); new Promise((res) => { console.log(a); }); // Captured promise exception: PromiseRejectionEventCopy the code

Note: This code will not catch the Promise exception if written directly to the console; it will catch the promise exception if written in an HTML file.

(4) window.onerror

Use 1.

When a JS runtime error occurs, the window raises an error event from the ErrorEvent interface and executes window.onerror().

Window. onerror = function(message, source, lineno, colno, error) {console.log(' Catch exception: ',{message, source, lineno, colno, error}); }Copy the code

2. The motivation

It is well known that many error monitoring and reporting libraries are based on this feature, and we expect it to handle attempts… Catch an error that cannot be handled.

Scope of 3.

According to MDN, wondow. Onerror can catch JavaScript runtime errors (including syntax errors) or some resource errors. In real testing, wondow. Onerror does not catch syntax errors.

When tested, window.onError did not catch syntax errors or static resource loading errors. It also cannot catch errors in asynchronous code, but it is worth noting that window.onerror can catch errors in setTimeout and setInterval, which are also asynchronous code.

It seems that window.onerror, with its high hopes, is not a panacea.

(5) window.addEventListener

Use 1.

window.addEventListener('error',(error)=>{console.log(error)})
Copy the code

2. The motivation

Onerror and try catch, so that it can catch asynchronous errors and resource loading errors.

Scope of 3.

<body> <img id="img" src="./fake.png" /> <iframe id="iframe" src="./test4.html"></iframe> </body> <script> window.addEventListener( "error", function (error) { console.log(error, "error"); }, true ); setTimeout(() => { console.log(a); }); new Promise((resolve, reject) => { console.log(a); }); Console. log(b) var f=e,Copy the code

During this process, the resource files are missing, and we find that window.addeventListener (‘error’) still fails to catch syntax errors,Promise exceptions, and iframe exceptions.

Syntax errors can be caught during compilation, and Promise exceptions have been addressed above, leaving iframe exceptions to handle separately.

(5) the iframe anomalies

Use 1.

Window.frames [0]. Onerror = function (message, source, lineno, colno, error) {console.log(' Catch iframe: ',{message, source, lineno, colno, error}); return true; };Copy the code

2. The motivation

Used specifically to catch exceptions during iframe loading.

Scope of 3.

Unfortunately, the results were not satisfactory, and during actual testing, the method failed to catch exceptions.

(6) React catches exceptions

JavaScript errors in part of the UI should not crash the entire app, and React 16 introduced a new concept called error boundaries to address this issue.

The error boundary is a React component that catches and prints JavaScript errors that occur anywhere in its child tree, and instead of rendering the broken child tree, it renders the alternate UI. Error bounds catch errors during rendering, in lifecycle methods, and in constructors throughout the component tree.

Note: Error boundaries do not capture errors generated in the following scenarios

  • The event processing
  • Asynchronous code (such as setTimeout or requestAnimationFrame callback)
  • Server side rendering
  • Errors thrown by itself (not by its children)

If either (or both) of the static getDerivedStateFromError() or componentDidCatch() lifecycle methods are defined in a class component, it becomes an error boundary. When an error is thrown, render the standby UI using static getDerivedStateFromError() and print the error message with componentDidCatch().

class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) {// Update state so that the next render can display the degraded UI return {hasError: true}; } componentDidCatch(error, errorInfo) {// You can also report error logs to logErrorToMyService(error, errorInfo); } render() {if (this.state.haserror) {// You can customize the UI and render return <h1>Something went wrong. } return this.props.children; }}Copy the code

Error boundaries work like JavaScript’s Catch {}, except that they only apply to React components. Only class components can be error bound components. In most cases, you only need to declare the error boundary component once and use it throughout the application.

React.

(7) Catch exceptions in Vue

ErrorHandler = function (err, vm, info) {// Handle error // 'info' is a Vue specific error message, Such as the lifecycle hook where the error occurred // only available in 2.2.0+}Copy the code

Specifies a handler that does not catch errors during rendering and viewing of the component. When this handler is called, it gets an error message and a Vue instance.

  • As of 2.2.0, this hook also catches errors in component lifecycle hooks. Similarly, when the hook is undefined, the caught error will be output console.error to avoid application crashes.
  • As of 2.4.0, this hook will also catch errors within Vue custom event handlers.
  • As of 2.6.0, this hook also catches errors thrown inside the V-ON DOM listener. In addition, if any overridden hook or handler returns a Promise chain (such as async function), errors from its Promise chain will also be handled.

The above quote is from Vue official website.

(8) The HTTP request is abnormal

Use 1.

In the case of Axios, add a response interceptor

Axios. Interceptors. Response. Use (function (response) {/ / do something / / response is the response data request data return back the response; }, function (error) {return promise.reject (error)})Copy the code

2. The motivation

Used specifically to catch HTTP request exceptions

V. Project practice

After putting forward so many solutions, WE believe that there are still some doubts about how to use them. So next, let’s really get into the practice stage!

What are the problems we need to solve again?

  • Grammar mistakes
  • Abnormal events
  • HTTP request exception
  • Static resource loading is abnormal
  • Abnormal Promise
  • The Iframe abnormal
  • Page collapse

Is catching exceptions our ultimate goal? No, going back to the context of solving the problem, reminding the user of what’s going on in the right way at the right time is definitely a friendlier way to deal with it than when the page crashes or clicks.

Combined with the project, there are two specific practices as follows:

  • 1. The code uses a lot of try catch/ promise. catch to catch
  • 2. Use the mechanism provided by the framework to do so, and then take the bottom out of what cannot be captured

Plan 1 is definitely not very smart… This means changing a lot of old code and multiplying the mental burden. The second option is more sensible, by uniformly handling errors at the bottom without changing the logic.

In the project, React framework was used. React provides a mechanism to catch exceptions (as mentioned above) and perform degradation. However, careful friends found that React could not catch the following four errors:

  • The event processing
  • Asynchronous code (such as setTimeout or requestAnimationFrame callback)
  • Server side rendering
  • Errors thrown by itself (not by its children)

As for the third point of server rendering error, there is no applicable scene in the project, so we will not focus on the analysis this time. Let’s focus on the first and second points.

I’m going to throw out a few questions for you to think about briefly:

  • 1. How can we prevent crashes caused by errors in event handling and asynchronous code?
  • 2. How to take the bottom out of ErrorBounary? How to be more user-friendly than a button click that doesn’t work?

Let’s start with the first problem. If errors in event handling and asynchronous code cause the page to crash:

const Test = () => {
  const [data, setData] = useState([]);
  return (
    <div
      onClick={() => {
        setData('');
      }}
    >
      {data.map((s) => s.i)}
    </div>
  );
};
Copy the code

This code is fine during normal rendering, but after the click event is triggered, the page will be abnormally white. What if we put our ErrorBounday component on top of it?

The answer is that errors can still be caught and the component can be degraded!

At this point, some of you have noticed that error boundaries can catch errors as long as they are during rendering, whether it is the first or second rendering. The flow chart is as follows:

The first problem turned out not to be a problem at all, it was itself a closed loop, we do not need to solve!

Let’s look at the second question:

For code in event handling and asynchronous code that does not cause the page to crash:

const Test = () => { return ( <button onClick={() => { [].map((s) => s.a.b); }} > click </button>); };Copy the code

Button button can be clicked normally, but the internal logic of the click event is defective, causing the user to click the button is essentially invalid. At this point, if you don’t give a friendly prompt in time, users will only be in a frenzy….

Is there any way to get the bottom of ErrorBoundary? That is, you can catch errors in asynchronous code or event processing.

The window.addeventListener (‘error’) mentioned above solves this problem. Ideally:

And the actual order of execution actually looks like this:

During the actual execution, window.addeventListener (‘error’) catches the error before ErrorBoundary, so when the error event catches the error, it does not know whether the error will cause the page to crash or not, and it does not know what prompt to give. Is the page degraded or just a simple error message?

The problem seems to be stuck here….

Is there an effective way to tell the error event that ErrorBoundary has caught the error and you don’t need to deal with it? Or maybe ErrorBoundary didn’t catch the error, it’s an asynchronous error/event error, but it doesn’t crash the page, you just need to prompt the user!

There is definitely an answer, such as setting up a nodeJs server and sending notifications through websockets, but this is not only a hassle, but also a bit of a delay.

While I was meditating, I had an inspiration one quiet night. Why do we have to follow his order? Can we try to change the order of his execution so that error catching can return to our desired flow?

After changing our thinking, can we change the order in which the code is executed? That’s right, asynchronous events!

Window.addeventlistener ('error', function (error) {setTimeout(()=>{console.log(error, 'error'); })});Copy the code

When setTimeout is added to the callback function of an error event, the process for catching an exception is as follows:

Now you can tell the error event whether the page crashed and whether it needs to be handled! The code:

class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) {// Update state so that the next render can display the degraded UI return {hasError: true}; } componentDidCatch(error, errorInfo) {// You can also report error logs to logErrorToMyService(error, errorInfo); SetItem ("ErrorBoundary",true)} render() {if (this.state.haserror) {// You can customize the degraded UI and render return <h1>Something went wrong. } return this.props.children; }}Copy the code
window.addEventListener('error', Function (error) {setTimeout(() => {// this indicates that there must be an error in the ErrorBoundary localStorage.getItem('ErrorBounary'); Localstorage.setitem ('ErrorBounary', false); if (flag) {localStorage.setitem ('ErrorBounary', false); LogErrorToMyService (error, errorInfo); logErrorToMyService(error, errorInfo); If (error.message.indexof ('TypeError')) {alert(' This is a TypeError, please inform developers '); } else if (error.message.indexof ('SyntaxError')) {alert(' This is a SyntaxError, please inform developers '); } else {// give friendly hints}}}); });Copy the code

Finally, through our efforts, when the page crashes, timely downgrade processing; When the page does not crash, but there are errors, we timely inform users and report the errors, to achieve the desired effect.

Six, extension,

1. Set the collection rate

If there are too many errors, for example, sometimes the code enters an infinite loop and the server is under great pressure due to too many errors, the collection rate can be reduced as appropriate. For example, 30% :

If (math.random () < 0.3) {// logErrorToMyService(error, errorInfo); }Copy the code

2. The effect

After solving the above problems, we will inevitably have a question: then each component needs to set a layer of ErrorBoundary component, is it a bit too much work…. And there are some old code, nested more deep, change the psychological burden will be larger. Is there a way to use it as a configuration item and then automatically overlay the ErrorBoundary component at compile time? We will discuss this next time!

3. Can be configured

Can ErrorBoundary be extended to a component that can be passed into a custom UI? This allows you to customize the UI to perform different downgrades in different scenarios.

Again, we’ll talk about this next time!

Seven,

Exception handling is an essential part of quality software development, but in many cases they are ignored or used incorrectly, and exception handling simply ensures that the code flow does not go wrong and is redirected to the correct program flow.

Starting from front-end error types, this paper gradually reveals the mysterious veil of error and anomaly from try catch, and then monitors and captures the anomaly through a series of operations, finally achieving the effect of improving user experience and reporting the monitoring system.

Eight, thinking

  • What is the difference between a promise. catch and a try catch?
  • How is ErrorBounary implemented internally?
  • Why unhandledrejection writing to the console does not catch errors? You can capture it in an HTML file, right?
  • How are server rendering errors caught?

With that in mind, we’ll see you next time