preface

I have always held the view that human beings have lived in a world full of mistakes in some way. Wrong environmental pollution, wrong urban design (underground sewage system, road design), wrong utilitarian society, wrong wind of comparison, wrong consciousness, wrong attitude, wrong action…… In short, it is human to err, and the world is full of mistakes. This is a basic fact.

The same applies to software programming. Similarly, the world of software programming is full of errors. Some mistakes are caused by external circumstances, and some mistakes are caused by our own negligence. Unlike the real world, we developers are not afraid to make mistakes in the software world, or to be more precise, to make small mistakes. Because we can always pass such errors on to the user at runtime, out of sight and out of mind. Obviously, this attitude and practice is unprofessional. In software engineering, ensuring software robustness is one of the main themes. Therefore, to properly deal with front-end errors is a reflection of the front-end developer’s software engineering level. I remember a backend developer friend of mine saying to me, “Sam, don’t you front-end developers handle exceptions? This is too unprofessional.” Yes, the front-end application is definitely not as focused on error/exception handling as the server application. Therefore, in order to demonstrate our professionalism as software engineers, we must pay attention to front-end error/exception handling and handle it well.

As the Chinese translation community has it, I switch between “error” and “exception,” which all mean the same thing.

To deal with front-end anomalies, we can not only reflect our professionalism as software engineers, but also connect to the front-end monitoring system. With the development of front-end rich applications and separated software development models, the scale of front-end code is getting bigger and bigger. In order to better trace the problem, locate the problem and solve the problem, the front-end monitoring system came into being. At present, the front-end monitoring system is only responsible for “error monitoring” and “performance monitoring”. Performance monitoring is not the subject of this article, so it will be skipped. In order to collect front-end errors from the front-end monitoring system on the server side, the problem for the front-end is “how does the front-end find and report errors?” .

Front-end application is the closest place for the whole software service to the user. In line with the business principle of “customer is God”, it is the responsibility of front-end developers to create good user experience for users. When a page error occurs, it is undoubtedly a friendlier way to remind the user of what is happening in an appropriate way at the appropriate time, rather than the page does not move.

Given all that, there are good reasons and incentives to deal with front-end errors. So let’s get started.

The body of the

Wrong type

Because, the product of Web front-end development is web pages. So, a front-end error is a page error. What types of errors are likely to occur in our pages during daily development? I can summarize the following types:

  1. ECMAScript exceptions
  2. DOMException and DOMError
  3. Network static resource loading error
  4. Script errors caused by cross-domain references to script
  5. Page collapse

1. ECMAScript exceptions

ECMAScript exceptions are errors that occur during javascript execution. Each type of error has a corresponding type of error. When an error occurs, an error object of the corresponding type is thrown. The following are the seven error types defined by ECMA-262 and one non-standard InternalError type.

  • Error

    Error is the base class for the rest of the Error types, so all of them share the same set of methods and attributes. Error errors are rare, and their main purpose is for developers to customize their own Error types.

  • EvalError

    This error is thrown if eval() is not called as a function. Such as:

    new eval(a);eval = foo;
    Copy the code

    In theory, browsers should throw an EvalError. But in reality, every browser behaves differently. Since EvalError is no longer used in the current ECMAScript specification, errors of this type are rare and will probably not be thrown by js engines in the future.

  • RangeError

    This error type is easier to understand. This error occurs when we initialize the array if the requested length is too long. That is, the error is triggered when the value is out of range. Such as:

    const foo = new Array(-1); const bar = new Array(Number.MAX_VALUE); // Infinite recursion breaks the maxmium size of the Call Stackfunction foobar(){
        foobar();
    }
    foobar();
    Copy the code
  • ReferenceError

    This type of error is usually triggered when accessing undeclared or nonexistent variables. Such as:

    console.log(a); // The browser will raise ReferenceError if a is accessed without declaring itCopy the code

    We see this type of error when we miswrite variable names.

  • SyntaxError

    As the name implies, this type of error occurs when we make syntactic errors in our javascript code. Such as:

    eval(1 + + 2); Constt a = 1; constt a = 1;function test(){
        retrun 'It should be R-E-T-U-R-N.';
    }
    Copy the code
  • TypeError

    This is the most common type of error we see at runtime. Because javascript is dynamically weakly typed, the data types of all variables are in flux. This well-known TypeError is reported by the browser when an inappropriate operation is performed on an inappropriate datatype variable during runtime. Such as:

    const o = new 10; // TypeError
    for(let key in 10){} // TypeError
    (10).slice() // TypeError
    Copy the code
  • URIError

    When using encodeURI() or decodeURI(), URIError is caused when the format of the URL for the passed character is incorrect. However, this error is actually very rare, because the two functions are very fault-tolerant.

  • InternalError

    This is an unstandardized error type. It refers to errors that occur in javascript Engine. Usually, it’s just too much of something. Such as:

    • We wrote too many switches… case… Statements;
    • Regular expressions have too many parentheses, and so on.

2. DOMException and DOMError

According to the data on MDN, DOMError is not a standard specification, so there is no need to discuss it here. DOMException is an error that occurs when we call a Web API method or property, or simply when we perform a DOM operation.

This type of error often occurs when calling Web API methods at the wrong time when working with audio. Such as:

window.onload = function () {
    const video = document.getElementById('video');
    video.play(); 
};


<video id="video" preload="none" src="http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4"></video>
Copy the code

The above code will report a DOMException:

Uncaught (in promise) DOMException: play() failed because the user didn’t interact with the document first.

Or when the DOM interface is incorrectly called:

<head>
    <script type="text/javascript">
        function ThrowDOMException () {
            var elem = document.createAttribute ("123");
        }
    </script>
</head>
<body>
    <button onclick="ThrowDOMException ()">Throw a DOM exception</button>
</body>
Copy the code

In the example above, the browser will report a DOMException:

Uncaught DOMException: Failed to execute ‘createAttribute’ on ‘Document’: The localName provided (‘123’) contains an invalid character.

3. Static network resources are loaded incorrectly

Common static resources on the web include HTML files, CSS files, javascript files, images, audio, video, iframe, and so on. All static resources on the network are subject to loading errors. The cause of the loading error may be a URL error, the server may not have the resource, the server may have an internal error, or the network is busy and the request times out, causing the resource loading failure. If the client fails to load the file for any reason, a loading error occurs.

4. script error

The browser throws this error when a javascript resource in another domain is referenced across domains and the javascript execution is wrong.

Suppose we have a js file under the domain name another-domain.com:

const a = {};
console.log(a.b.c);
Copy the code

In our own domain origin-domain.com, we refer to this file:

<script src="http://another-domain.com/index.js"></script>
Copy the code

In this case, the browser will throw a script error. A script error type basically tells you that the script execution of your cross-domain reference went wrong, but you have no right to know the exact error message. It’s all because browsers have what’s called a same-Origin policy for information security.

5. page crash

When you access an unreliable Web application, it can do something crazy and unpredictable, which can crash the entire browser. Your page suffers as a result. Under Chrome, the page crashes like we’ve all seen before:

Error prevention

In my opinion, the attitude towards code errors should be as follows: if you can prevent them, you should do a good job in preventing them and nip them in the bud. The above five types of error, except for the first, the rest of the several are almost impossible to guard against.

DOMException is usually a mistake you make when you misunderstand or remember the DOM API. You subconsciously think you might be right before you make a mistake. So in this case, there is no artificial prevention. For the others, the source of the error is almost out of your control, so you can’t prevent it. In this section, I’ll focus on how we can prevent ECMAScript exceptions, which are the types of errors we can prevent. In fact, many of these errors can be alerted and corrected at coding time when typescript is introduced and javascript is protected by a type jacket. However, this section wants to talk about how to prevent these types of errors by developing some good javascript programming habits.

In the ECMAScript Exceptions type, we mainly guard against three subtypes of errors:

  • Errors caused by implicit type conversions
  • Errors caused by lack of type defense
  • Communication error

Errors caused by implicit type conversions

This error is usually the result of not being vigilant about implicit type conversions. Implicit type conversions are provided as a feature of PL, and there may be nothing wrong with them, but what’s wrong with them is that you didn’t measure their subsequent impact.

For example, you might have an ID of numeric type and request a list back from a back-end interface. If the ID of an item in the list is equal to the ID you have, then you want to highlight the border around the item. Sometimes it returns a string, sometimes it returns a number, so you write:

this.list = this.list.map(item=> {
    if(item.id == myID){
        return {
            ...item,
            iSelected: true}}return item;
})
Copy the code

Yes, the implicit conversion of the equals sign instead of the full equals sign is useful for fault tolerance. So there’s nothing wrong with implicit conversions, but you know how to use them. At the end of the day, however, invisible conversions make code less predictable and harder to maintain. Personally, even if you know you’re using implicit casts, it’s probably best not to use them. The above code can be cast:

if(parseInt(item.id) === parseInt(myID)){
    ......
}
Copy the code

It is well known that when using equality (==) and inequality (! The ==) operator, or when process control statements such as if,for, and while use non-Boolean data, we are essentially using implicit conversions.

It is worth adding that implicit type conversions also occur when a non-string key is used to access an object’s attribute value, such as:

const obj = {1:'implicitly convert'};
obj[1] === obj['1'=== obj[1.0] === obj[+1]; //true// To convert an object type to a string, we start with the object itself and look for the toString method along the prototype chain. var o = { toString:function () { return "1.5"; }}; Obj [1.5] = = = obj [o]; //true
Copy the code

When you become slightly less alert to implicit type-casting, you make a mistake. For example, in financial management business development, we have an input box, which is mainly used to input the purchase amount. At the bottom of the input box, we will have a prompt. Follow up the user’s input in real time and calculate the interest generated by the purchase amount simultaneously:

let hintText = 'Please enter purchase amount';
const purchaseAmount =parseFloat(document.getElementById('input').value) ;

if(purchaseAmount) { const income = .... // Do the four arithmetic operations; HintText = 'expected revenue is $(income)'; }Copy the code

Because the user might have typed “0”, hintText could not be updated because the implicit type of the if statement was converted to false. As a result, the user enters something, but the default prompt “Please enter the purchase amount” is displayed. Not a fatal error, but clearly an illogical one.

Here, we would not have made this mistake if we had avoided implicit type conversions:

if(typeof purchaseAmount === 'number' && !isNaN(purchaseAmount)){
    // ......
}
Copy the code

While the error here will not result in a runtime error in the JS code, the next time an improper type is slipped into the block of code in the if statement, and then improper operations are performed on the data of that type, possibly resulting in a TypeError error.

Therefore, we should avoid implicit conversions. How to avoid it?

  • Use full equals (===) and partial equals (! ==) to replace the equality sign (==) and inequality sign (! =);
  • In various flow control statements, make sure that a Boolean value (or an expression with a Boolean value) is passed in when making a conditional judgment.

Errors caused by lack of type defense

Javascript is a dynamically weakly typed programming language. Therefore, in the process of using JS software development, in order to ensure the robustness of the program, type defense is too important.

For example, let’s implement a function that fetches a Query String from a URL:

function getQueryString(url){
    const pos = url.indexOf('? ');
    if (pos > -1) {
        return url.slice(pos + 1);
    }
    
    return ' ';
}
Copy the code

We take it for granted that urls are strings. But what if the person calling our function accidentally passes in a numeric value? Obviously, here, an inappropriate method is called on an inappropriate data type, and the browser will give us a large “Uncaught TypeError: url.indexof is not a function” at runtime. This mistake can be avoided by increasing our vigilance and adding a simple type defense statement:

function getQueryString(url){
    if(typeof url === 'string') {
        const pos = url.indexOf('? ');
        if (pos > -1) {
            returnurl.slice(pos + 1); }}return ' ';
}
Copy the code

In type defense, how do you prepare to determine the type of data? I can summarize it in two ways.

The first is determined by the native operator. For primitive data types, we can just use the Typeof operator:

typeof undefined === 'undefined' // true
typeof 'str'= = ='string' // true
typeof 123 === 'number' // true
typeof true= = ='boolean' // true
typeof Symbol('react') = = ='symbol' // true
Copy the code

It’s worth noting that NULL is a bit special, since it’s a billion dollar error. The value of typeof NULL is an “object”. Therefore, we can judge as follows:

// const n = null; n === null //trueOr use jquery: String(n) ==='null' // true
Copy the code

For object types, using the typeof operator is not enough. Instead, use the “instanceof” operator:

const obj = {};
const fn = ()=> {};
const arr = [];
const date = new Date();
const reg = /^[a-z]$/;
const promise = new Promise(reslove=> reslove())


obj instanceof Object // true
fn  instanceof Function // true
arr instanceof Array // true
date instanceof Date // true
reg instanceof RegExp // true
promise instanceof Promise // true
Copy the code

The second is an all-purpose approach that smacks of hack:

function typeOf(obj){
    const stringResult = Object.prototype.toString.call(obj);
    const matchResult = stringResult.match(/^\[(\w+)\s+(\w+)\]$/);
    
    if(matchResult ! == null){return matchResult[2].toLowerCase();
    }
    
    throw new Error('Unknown type');
}

const obj = {};
const fn = ()=> {};
const arr = [];
const date = new Date();
const reg = /^[a-z]$/;
const promise = new Promise()

typeOf(null) === 'null' // true
typeOf(undefined) === 'undefined' // true
typeOf(1) === 'number' // true
typeOf('hello world') = = ='string' // true
typeOf(true) = = ='boolean' // true
typeOf(Symbol('react'= = =))'symbol' // true
const promise = new Promise(reslove=> reslove())

typeOf(obj) === 'object' // true
typeOf(fn) === 'function' // true
typeOf(arr) === 'array' // true
typeOf(date) === 'date' // true
typeOf(reg) === 'regexp' // true
typeOf(promise) === 'promise' // true
Copy the code

As you can see, the typeOf method is designed to accurately determine the typeOf all javascript values. This is “home travel, kill” essential medicine ah.

Communication error

In the process of front-end development, the general contact with the communication type includes: front-end and client communication, front-end and server communication.

In the communication between the front-end and the client, it is common for the front-end to pass an unencoded URL to the client, resulting in client parsing errors. Such as:

ssj://login? redir=https://frontend.com/index.html?a=b&c=dCopy the code

EncodeURIComponent () encodeURIComponent()

ssj://login? redir=https%3A%2F%2Ffrontend.com%2Findex.html%3Fa%3Db%26c%3DdCopy the code

The client side also needs to use the matching method to decode before parsing.

In the process of communicating with the server, many things are worth noting. Many times, the back end makes the mistake of not returning the data type and structure as specified by the convention even though the back end has specified the data type and structure through the interface documentation.

Here are some common backend errors regarding data types:

  • Confusion between numeric and string types
  • Null confusion with “null”

When it comes to data structures, the back end often does not return by convention. For example, we agreed to return a data structure like this:

{
    success: true,
    msg: ' ',
    data: {
        list: []
    }
}
Copy the code

What he returned to me was this:

{
    success: true,
    data: {
        msg: ' ',
        data: []
    }
}
Copy the code

Alternatively, the returned JSON sequence string is syntactically incorrect, causing the front end to parse json.stringify () with an error.

In this case, if we put too much trust in the data returned by the communicator, and do not defend the type and field accordingly, then our JS code is most likely to run TypeError.

Error handling

However, good precautions and the tools of Typescript can prevent many low-level errors from happening. But the worst is always the best. It doesn’t matter if you make mistakes, what matters is knowing how to deal with them. How do you deal with that? I break it down into two steps:

  • Step 1: catch exceptions and get error information;
  • The second step: according to the actual demand and do the corresponding processing;

Step 1: Catch the exception and get the error message

There are several techniques for catching exceptions on a page:

  • try… catch
  • window.onerror
  • window.addEventListener(‘error’,()=>{})
  • element.onerror
  • Promise Catch and window.addeventListener (“unhandledrejection”,()=> {})
  • The iframe and iframe. Onload
  • other

The following are detailed explanations.

try… catch
  1. motivation

    Using a try… I can generalize that there are two main motivations for catching exceptions: 1) you really want to catch exceptions in code that might 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);
    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.

    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 Chromev81.0.4044.92. 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: “Sync code + sync Code” case:

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

    • Scenario 2: “Synchronous code + Asynchronous Code”

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

    • Scenario 3: “Asynchronous code + Synchronous code” :

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

    • Scenario 4: “Asynchronous code + asynchronous code” case:

    Bad asynchronous code does not affect subsequent asynchronous code execution.


    We focus on scenarios 2 and 3 if we understand the reason for their results as: “That’s because synchronous code always executes before asynchronous code at runtime. If there is no error in the first synchronized code, then the following code will execute normally, otherwise the following code will not execute “, so let’s look at this example:

    At runtime, the JS engine should first execute our synchronization code “console.log(a);” . At this point, our synchronous generation failed, and according to our preliminary conclusion, the asynchronous code following the execution stream should not be executed. In fact, both of them are executed, right? Let’s look at another example:

    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 other words, the asynchronous code doesn’t have to go wrong or not. 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.

  2. grammar

    try {
       try_statements
    }
    [catch (exception_var_1 if condition_1) { // non-standard
       catch_statements_1
    }]
    ...
    [catch (exception_var_2) {
       catch_statements_2
    }]
    [finally {
       finally_statements
    }]
    Copy the code

    That is to say, there are the following combinations:

    • try… catch
    • try… finally
    • try… catch… finally

    Catch can be concatenated in multiple forms, which is called Conditional catch-blocks because it is not a standard specification and is no longer supported in many forms of browsing, so I will not discuss it here.

    Using a try… Catch catch catch catch catch catch catch catch catch

    • The catch clause receives an error object. Even if you don’t want to use the error object, you must give it a name, otherwise the browser will report an “Uncaught SyntaxError”.

    • The finally clause executes anyway and takes precedence over the return statement. For example, the following function returns 0 instead of 2:

      function testFinally(){
          try{
              return 2;
          }catch(e){
              return 1;
          }finally{
              return0; }}Copy the code
    • try… Catches can nest with each other. An error thrown inside will be caught by the nearest catch block to the error. This “recent” includes two cases. The first type is the catch block nearest to it in the same level (since only Firefox series 1-59 supports this in browsers, the relevant situation of Conditional catch-blocks can be ignored). The second type is the catch block in the nearest level in the different levels.

      try {
        try {
          throw new Error("oops");
        }
        catch (ex) {
          console.error("inner", ex.message);
          throw ex;
        }
        finally {
          console.log("finally");
        }
      }
      catch (ex) {
        console.error("outer", ex.message);
      }
      
      // Output:
      // "inner" "oops"
      // "finally"
      // "outer" "oops"
      Copy the code
  3. They can handle

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

Tips: syntax errors are found during the compilation phase of the JS engine, when your JS code has not been executed. Therefore, syntax errors cannot be caught by your JS code.

Could not catch syntax error:

Unable to catch errors when asynchronous code is running:

One special case, however, is Async… Await. The error that occurs with asynchronous code taken over by await is to be able to use try… 3. To catch:

About the try… Catch, this article draws a conclusion about the exceptions it can catch from a relatively new perspective:

Exceptions that can be caught by a try catch must be caught when the thread is already inside the block at the time of the error.

Whether this statement is correct and sufficiently rigorous remains to be demonstrated and verified.

window.onerror
  1. 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.

  2. grammar

window.onerror = function(message, source, lineno, colno, error) { ... }
Copy the code

For historical reasons, the window.onerror event handler receives different parameters in different browsers or different versions of the same browser. This is just a discussion of the standard specification:

  • Message: string of error messages
  • Source: URL of the error js file (string)
  • Lineno: Line number of error code
  • Colno: Column number of the error code
  • Error: Error object

As mentioned above, because the parameters passed to the onError event handler, except message, are incompatible across browsers, it is recommended to use only the Message parameter for browser compatibility purposes.

It is worth mentioning here that if you want window.onError to catch an error and cancel its default behavior (which is to print an uncaught xxxxError on the browser console), the event handler needs to return true. This goes against common sense. Because in UI event systems, undoing the default behavior is usually done by returning false.

  1. They can handle

Now comes the part we’re most concerned about. What kind of errors can window.onError handle?

As stated in MDN:

When a JavaScript runtime error (including syntax errors and exceptions thrown within handlers) occurs, an error event using interface ErrorEvent is fired at window and window.onerror() is invoked

This makes it sound as if all runtime javascript errors, window.onerror, could be caught. In fact, this statement has been proven to be incorrect on chromevxxx and firefoxv75.0. Window.onerror is not a panacea.

  1. Window. onerror cannot catch syntax error:

    window.onerror = function(message, source, lineno, colno, error) {
        console.log('Exception caught with window.onerror:',message);
        return true;
    }
    
    const a = 1'; // Syntax errorCopy the code

    As you can see, the syntax error is not caught and is displayed on the browser console.

  2. Window. onerror cannot catch errors in asynchronous code

    Onerror can catch errors in setTimeout and setInterval, which are also asynchronous code:

     window.onerror = function(message, source, lineno, colno, error) {
        console.log('Exception caught with window.onerror:',message);
        return true;
    }
    
    setTimeout(()=> { console.log(a); }, 0)setInterval(() => {
         console.log(b);
     }, 1000);
    
    Copy the code

  3. Window. onerror cannot catch loading failures of static resources.

  4. As stated in MDN, window. onError is used to catch exceptions, including syntax errors, that occur in event handlers.

    <script> window.onerror = function(message, source, lineno, colno, error) {console.log(' window.onerror: ',message); return true; } </script> <img id="img" src="./fake.png" onerror="console.log(abc)"> ```Copy the code

    The console will print:

    Exception caught by window.onerror: Uncaught ReferenceError: ABC is not defined

    It seems that window.onerror is not a panacea. We still expect it to completely try… As a result, it also has errors that it cannot catch.

Window. The addEventListener (‘ error ‘() = > {}) and element onerror
  1. motivation

    Since window. onError cannot catch network resource load failures, we hope that these two techniques will meet our needs.

  2. grammar

    As MDN says:

    When a resource (such as an <img> or<script>) fails to load, an error event using interface Event is fired at the element that initiated the load, and the onerror() handler on the element is invoked. These error events do not bubble up to window, but (at least in Firefox) can be handled with a window.addEventListener configured with useCapture set to True.

    As you can see, the two are on the same event propagation path: one is target Phase, and the other is Capture Phase. Verified, Firefox and Chrome are able to catch this type of error by setting the second parameter of window.addeventListener () to true.

  3. They can handle

    Let’s run an example to see if window.addeventListener (‘error’) really does what we expect:

    <! DOCTYPE html> <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="Width = device - width, initial - scale = 1.0"> <title> Network resource loading error catch </title> <script> window.addeventListener ('error', (error) => {
                console.log('Window. addEventListener can catch exceptions:', error);
            }, true)
        </script>
        <script src="./fake.js"></script>
        <link rel="stylesheet" href="./fake.css">
    </head>
    <body>
        <img id="img" src="./fake.png">
        <video id="video" src="fake.mp4"></video>
        <audio src="fake.mp3"></audio>
        <iframe id="iframe" src="./test4.html" frameborder="0" ></iframe>
    </body>
    </html>
    Copy the code

    In this, all the network resources are non-existent, should be able to report errors. Let’s take a look at the console print:

    As you can see, all resource loading errors except iframe are caught by window.addeventListener (‘error’).

    Looks like iframe needs to be treated differently.

Promise Catch and window.addeventListener (“unhandledrejection”,()=> {})
  1. motivation

    SetTimeout /setInterval can be caught by window. onError, async… Await means to be tried… Catch, catch, catch Now, let’s look at how to catch errors in the Promise code.

  2. grammar

    const promiseObj = new Promise(executor);
    
    promiseObj
    .then(handleFulfilledA,handleRejectedA)
    .then(handleFulfilledB,handleRejectedB)
    .then(handleFulfilledC,handleRejectedC);
    
    // 或者
    promiseObj
    .then(handleFulfilledA)
    .then(handleFulfilledB)
    .then(handleFulfilledC)
    .catch(handleRejectedAny);
    Copy the code

    MDN recommends the latter. The syntax is more like try… Catch is easier to accept.

  3. They can handle

    Currently, there is no technical way to catch the exceptions that promises generate, and the catch that comes with promises comes just in time. Let’s verify:

    Yes, it is. However, sometimes either you or your colleagues forget to write a catch branch, so it would be wise to add a global listener to the unhandledrejection event.

    window.addEventListener("unhandledrejection".function(e){
        console.log('Caught Promise exception :', e);
        e.preventDefault();
    });
    new Promise((res)=>{console.log(a)});
    Copy the code

    It’s worth noting here that, unlike DOM1 which returns true/false, DOM3’s event mechanism is event.preventDefault() to cancel the event’s default behavior. Again, the default behavior here is “print an uncaught xxxxError on the browser console.”

The iframe and iframe. Onload

As mentioned above in the window.addeventListener (‘error’) section, it can catch loading errors of other types of network static resources except iframe. So what do we do with the iframe?

Use the onError attribute to simply see the error event that listens for an iframe, as follows:

window.frames[0].onerror = function (message, source, lineno, colno, error) {
    console.log('Iframe exception caught:',{message, source, lineno, colno, error});
    return true;
};
Copy the code

Either the above DOM1 or DOM0 writing method, after my test, seems to be not good. Out of frustration, I went to Google and found that someone had mentioned a bug to the Chromium team. The problem reported by the netizen was exactly the one I had encountered during the experiment. The following paragraph has been made clear:

The specification requires that all HTML elements support on onerror event. However, it does NOT require that all elements supporting network fetches raise fire a simple event called onerror. That is, elements must support allowing applications to set error handlers, but there is no (generic) requirement that the event be raised, in either HTML or the Fetch specification.

For now, this is WontFix, for Working as Intended (and as Specified)

The specification only requires us to implement support for onError event listening for all HTML elements, but does not require us to actually raise such an event. Especially for iframe elements, we felt there were network probing and other security risks.

The final conclusion: WontFix. In the end, we were forced to adopt some hack methods:

<! DOCTYPE html> <html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
 </head>
    <iframe 
        id="iframe" 
        src="./test22.html" 
        frameborder="0" 
        onerror="console.log(event)"
        onload="console.log(this.contentWindow.document.title)"
    >
    </iframe>
</body>
</html>
Copy the code

Let’s take a look at the printing of each browser’s console:

Chromev81.0.4044.92:

Firefoxv75.0:

Microsoft Edgev44.18362.329.0:

As you can see, the onError event handler for iframe is not executed. Furthermore, if the load fails, the document title of the page loaded by the iframe is set to “Error”. Finally, the solution we implemented with window.onerror looks like this:

<! DOCTYPE html> <html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
 </head>
    <iframe 
        id="iframe" 
        src="./test22.html" 
        frameborder="0" 
    >
    </iframe>
    <script>
        window.onerror = function(message, source, lineno, colno, error) { 
            console.log('Exception caught with window.onerror:',message);
            return true;
        }

        const iframe = document.getElementById('iframe');
        iframe.onload = function(event){
            const title = this.contentWindow.document.title.toLowerCase();
            if(title === 'error'){
                throw new Error('Iframe load failed');
            }
        }
    </script>
</body>
</html>
Copy the code

The result is that the console prints:

Exception caught by window.onerror: Uncaught Error: Iframe failed to load

other
  1. page crash

If you want to handle page crash, you can refer to this blog post. The solution is this:

 if(sessionStorage.getItem('good_exit') &&
      sessionStorage.getItem('good_exit')! = ='true') {
      /*
         insert crash logging code here
     */
      alert('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash'));
  }
  
window.addEventListener('load'.function () {
      sessionStorage.setItem('good_exit'.'pending');
      setInterval(function () {
         sessionStorage.setItem('time_before_crash', new Date().toString());
      }, 1000);
   });

   window.addEventListener('beforeunload'.function () {
      sessionStorage.setItem('good_exit'.'true');
   });

  
Copy the code

Obviously, this is the same as iframe. All we know is that the page crashed, but the exact cause of the crash or the errorEvent object we were expecting doesn’t exist.

This solution relies primarily on one fact. Page crashes cannot trigger beforeUnload events. So if the page crashes, good_exit logged in sessionStorage must be “pending”. Then the next time the page comes in, the if judgment condition is true, and we can do something about the last page crash. Such as giving users a friendly reminder, or sending the last crash information to the front-end error monitoring system and so on.

Another solution is to use service worker to do this. For details, please refer to how to monitor web crashes. This article.

  1. script error

There seems to be no surefire way to handle errors of the script error type. When faced with such errors in development, we generally have two options: 1) bypass the same-origin policy and eliminate the errors at their source; 2) When the third-party domain is under our control, we can try to use hack to capture specific third-party JS exception information.

There are two ways to circumvent the same-origin policy restriction, see Script Error — What causes a Script error and how to solve them.

The first is CORS. Add crossorigin=”anonymous” to the

The second is to use a Web proxy server. This is the normal way to deal with cross-domains.

When the third party domain is under our control and we do not want to use the first processing mode, we can use hack to solve script error in certain feature situations, specifically refer to the alternative idea of solving “Script error “. It is worth noting that some netizens are right about the conclusion of this article:

In other words, the solution described in this article only solves a special case of Script Error – the error in the event handler. Based on the fact that browsers don’t block try-catch exceptions across domains, hack into native addEventListener methods to catch errors in event handlers in third-party domain scripts. That’s how it works. However, this scheme does not catch errors outside of other scenarios. So, it’s not a one-size-fits-all solution. Currently, I don’t see a generic solution that can catch all script errors without breaking the same-origin policy constraint.

The second step: according to the actual demand and do the corresponding processing;

The first step above is how to find the appropriate solution for each error scenario. The purpose of processing is simply to know what went wrong or what went wrong.

So the second step is how to deal with these error messages. This can be done in the following two ways:

  • Report the error information to the front-end monitoring system.
  • Through the interface feedback, inform the user that the current page error.
Report the error information to the front-end monitoring system

Errors often need to be graded before they are reported to the monitoring system. How to rate is a matter of individual, or business by business. In general, we can basically divide it into two classes: fatal and non-fatal.

Non-fatal errors can be identified by one or more of the following criteria:

  • Does not affect the user’s primary task
  • Only a portion of the page is affected
  • Can be restored
  • Repetition eliminates errors

Fatal errors can be identified by one or more of the following conditions:

  • The application simply cannot continue to run;
  • The error clearly affects the user’s major actions;
  • Can lead to other associated errors.

Generally speaking, these are non-fatal errors such as image loading failure, API request timeout due to network reasons, etc. Basically, non-fatal errors are not a concern. When we monitor systems to troubleshoot and locate problems, our primary concern must be fatal problems.

In general, there is no initiative through try… An error that is eventually caught by window.onerror at the bottom of the pocket, most likely causing the application to stop running or affecting the user’s main operation. This type of error is a priority to troubleshoot and fix.

In some cases, if a page refresh is necessary for the application to continue running properly, the user must be notified and given a button to refresh the page with a click.

Determine the level of error, add the error information we get, and we can report it to the monitoring system.

A common solution is to use Image objects to send requests, whether for burying or error reporting. This has the following advantages:

  • Browser compatibility is good. Image objects are supported by all browsers.
  • You can avoid same-origin-policy restrictions. In real development, one server is usually responsible for receiving all errors from the server. In this case, there is a cross-domain problem that XMLHttpRequest alone cannot solve.
  • In the reporting process, the probability of error is relatively low. If you wrap an Ajax library or use an external Ajax library and expect it to report if the library itself is faulty, you can expect it to fail

Here, we simply implement a method for reporting errors:

function postError(type, msg) {
    const img = new Image();
    img.src = `log.php?type=${encodeURICoponent(type)}&msg=${encodeURICoponent(msg)}`}Copy the code
Through the interface feedback, inform the user that the current page error

When errors occur on the page, especially those that are expected to result in users not responding when they click on the page, be sure to give some friendly reminders and respect the user’s right to know.

The prompt must first be friendly and understandable to the user. The second is relative accuracy. There are usually toast, pop-up, partial page replacement and whole page replacement (similar to the common 404 error). The specific mode depends on the specific business scenario and error level.

Generally speaking, error reporting and interface feedback are best used together to achieve the best production effect.

conclusion

Summarize the core of this article with a picture:

The resources

  • try… Catch:developer.mozilla.org/zh-CN/docs/…
  • DOMException:developers.google.com/web/updates…
  • DOMException:help.dottoro.com/ljushpcx.ph…
  • Script error: raygun.com/blog/script…
  • Type judgment: programmer. Group/javaascript…
  • Window. Onerror: developer.mozilla.org/en-US/docs/…
  • The IFrame onerror bug: bugs.chromium.org/p/chromium/…
  • Logging Information on Browser Crashes: Logging Information on Browser Crashes