This article was originally published on the public account CoyPan as expected

Writing in the front

In front end projects, because JavaScript itself is a weakly typed language, coupled with the complexity of the browser environment, network issues, and so on, it is easy to make mistakes. It is very important to monitor web errors, optimize code and improve code robustness. This article will start with Error and show you how to catch exceptions in a page. The article is longer, more details, please be patient to watch.

Error in front-end development

In JavaScript Error

In JavaScript, Error is a constructor that creates an Error object. When a runtime Error occurs, an instance object of the Error is thrown. The syntax for constructing an Error is as follows:

// message: error description
// fileName: Optional. The fileName property value of the Error object being created. The default is the name of the file in which the Error constructor code is called.
// lineNumber: Optional. The value of the lineNumber property of the Error object being created. The default is the line number of the file in which the Error constructor code is called.

new Error([message[, fileName[, lineNumber]]])
Copy the code
ECMAScript standard:

Error has two standard attributes:

  • Error.prototype.name: Wrong name
  • Error.prototype.message: Incorrect description

For example, enter the following code in the Chrome console:

var a = new Error('Error test');
console.log(a); // Error: Indicates an Error test
    			// at <anonymous>:1:9
console.log(a.name); // Error
console.log(a.message); // Error test
Copy the code

Error has only one standard method:

  • Error.prototype.toString: returns a string representing an error.

The following code:

a.toString();  // "Error: Error test"
Copy the code
Nonstandard attributes

Each browser vendor has its own implementation of Error. Such as the following attributes:

  1. Error.prototype.fileName: Error file name.
  2. Error.prototype.lineNumber: Generated an error line number.
  3. Error.prototype.columnNumber: Generated an error column number.
  4. Error.prototype.stack: Stack information. This is a common one.

These attributes are not standard and should be used with caution in production environments. But modern browsers almost all support it.

The Error of the species

In addition to the generic Error constructor, JavaScript has seven other types of Error constructors.

  • InternalError: creates an instance of an exception thrown that represents an InternalError in the Javascript engine. For example: “Too many recursions”. Non-ecmascript standard.
  • RangeError: A numeric variable or parameter is out of its valid range. Var a = new Array(-1);
  • EvalError: Error related to eval(). Eval () itself is not executed correctly.
  • ReferenceError: ReferenceError. Example: the console log (b);
  • SyntaxError: indicates a SyntaxError. Example: var a =;
  • TypeError: A variable or parameter is not in a valid range. Example: [1, 2]. The split (‘. ‘)
  • URIError: Invalid argument passed to encodeURI or decodeURl(). Example: decodeURI (‘ % 2 ‘)

When something goes wrong while JavaScript is running, one of the eight errors (the seven above plus common error types) is thrown. The error type is available through error.name.

You can also construct your own Error type based on Error, so I won’t expand it here.

Other errors

All of the above are errors that occur when JavaScript itself runs. There are other exceptions in the page, such as DOM manipulation.

DOMException

DOMException is a W3C DOM core object that represents an exception that occurs when a Web Api is called. What is a Web Api? The most common is a set of methods for DOM elements, as well as XMLHttpRequest, Fetch, etc., which I won’t cover here. Take a look at the following DOM manipulation example:

var node = document.querySelector('#app');
var refnode = node.nextSibling;
var newnode = document.createElement('div');
node.insertBefore(newnode, refnode);

Uncaught DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
Copy the code

From the perspective of JS code logic alone, there is no problem. But the code does not operate according to DOM rules.

The syntax of the DOMException constructor is as follows:

// message: optional, error description.
// name: Optional, wrong name. Constant, specific value can be found here: https://developer.mozilla.org/zh-CN/docs/Web/API/DOMException

new DOMException([message[, name]]);
Copy the code

DOMException has the following three properties:

  1. DOMException.code: Indicates an incorrect number.
  2. DOMException.message: Incorrect description.
  3. DOMException.name: Error name.

For example, the above error code throws the values of the DOMException properties:

code: 8
message: "Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node."
name: "NotFoundError"
Copy the code
PromiseGenerated exception

In a Promise, if the Promise is rejected, an exception is thrown: a PromiseRejectionEvent. Note that Promise is rejected in both cases:

  1. The business code itself is calledPromise.reject.
  2. PromiseError code in.

Most of the constructors of PromiseRejectionEvent are currently incompatible in browsers, but not here.

There are two properties of a PromiseRejectionEvent:

  1. PromiseRejectionEvent.promise:rejectthePromise.
  2. PromiseRejectionEvent.reason:PromiseberejectThe reason why. Will pass toreject.PromsiethecatchParameter in.
Error loading resources

Due to network, security and other reasons, web page load resource failure, request interface error, is also a common error.

Summary of mistakes

When a web page is running, there are four possible errors:

  1. Exceptions thrown by the language itself while JavaScript is running.
  2. An exception occurred when the JavaScript was calling the Web Api. Procedure
  3. A rejection in a Promise.
  4. An exception occurred when the page loaded resources and called the interface. Procedure

I think, for the first two kinds of errors, we do not need to distinguish in the usual development process, can be unified into: [code error].

Capture the error

Web page errors occur, how do developers catch these errors? The following methods are common.

try… catch…

try… The catch… Everyone is familiar with it. Typically used to catch errors in specific code logic.

try {
  throw new Error("oops");
}
catch (ex) {
  console.log("error", ex.message); // error oops
}
Copy the code

When an exception occurs in the try-block code, it can be caught in the catck-block so that the browser does not throw an error. However, this approach does not catch errors in asynchronous code such as:

try {
    setTimeout(function(){
        throw new Error('lala');
    },0);
} catch(e) {
    console.log('error', e.message);
}
Copy the code

At this point, the browser will still throw an Error: Uncaught Error: lala.

Imagine if we divided all the code properly and wrapped it in try catch. Would we be able to catch all errors? You can do this with a compilation tool. However, try catch is performance-intensive.

window.onerror

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

Function parameters:

  • message: Error message (string)
  • source: script URL (string) where the error occurred
  • lineno: Line number (digit) where the error occurred
  • colno: Column number (digit) where the error occurred
  • error: Error object (object)

Note that if this function returns true, it prevents execution of the browser’s default error handlers.

window.addEventListener(‘error’)

window.addEventListener('error'.function(event) {... })Copy the code

We call the Object. The prototype. ToString. Call (event), returns the [Object ErrorEvent]. You can see that the Event is an instance of the ErrorEvent object. ErrorEvent is an Event object that is generated when a script fails and inherits from an Event. Since this is an event, you can get the Target property. ErrorEvent also includes information about when an error occurred.

  • ErrorEvent. Prototype. Message: string, contains a description of the error information.
  • ErrorEvent. Prototype. Filename: string, contains errors script file filename.
  • ErrorEvent. Prototype. Lineno: digital, contains the error occurs when the line number.
  • ErrorEvent. Prototype. Colno: digital, contains the error occurs when the column number.
  • ErrorEvent. Prototype. Error: an error occurred when an error object.

Notice that the ErrorEvent. Prototype. Corresponding error object, the error is the error mentioned above, InternalError, RangeError, EvalError, ReferenceError, SyntaxError, TypeError, URIError, one of domExceptions.

window.addEventListener(‘unhandledrejection’)

window.addEventListener('unhandledrejection'.function (event) {... });Copy the code

When a Promise is used, if a catch block is not declared, the Promise exception is thrown. Only in this way, or window. Onunhandledrejection can capture to the exception.

An event is the PromiseRejectionEvent mentioned above. We just need to focus on its reason.

Window.onerror and window.addeventListener (‘error’)

  1. The first is the difference between event listeners and event handlers. Listeners can only be declared once, and subsequent declarations overwrite previous ones. Event handlers can bind multiple callback functions.
  2. When a resource (img or script) fails to load, the element that loaded the resource fires oneEventOf the interfaceerrorEvent and executes on that elementonerror()Processing functions. But these error events do not bubble up to the window. However, theseerrorEvents can bewindow.addEventListener('error')Capture. In other words, ** can only be used when the resource fails to loadwindow.addEventListerner('error').window.onerror* * is invalid.

Summary of error catching

In my opinion, try{}catch(){} can be used to catch errors in the development process, so as to avoid hanging pages. For global error catching, in modern browsers I would prefer to just use window.addeventListener (‘error’), window.addeventListener (‘unhandledrejection’). For compatibility, add window.onerror, which is used together, and window.addeventListener (‘error’) is used specifically to catch resource loading errors.

Cross-domain Script Error, Script Error

In the process of Error capture, most of the time, you do not get the complete Error information, only get a “Script Error”.

The reasons causing

Since 12 years ago this article mentioned safety problems: blog.jeremiahgrossman.com/2006/12/i-k…

When a syntax error occurs in a Script loaded from a different domain, to avoid information disclosure, the details of the syntax error are not reported. Instead, a simple “Script error.” is used.

Generally speaking, the JS files of the page are stored in the CDN, and the URL of the page itself causes cross-domain problems, thus causing “Script Error”.

The solution

Add access-Control-allow-Origin to the server and set crossorigin=”anonymous” in the script tag. In this way, the “Script Error” problem caused by cross-domain is resolved.

To circumvent theScript Error?

The standard solution for “Script Error” was described above. However, not all browsers support Crossorigin =”anonymous”, and not all servers can configure access-Control-allow-Origin in a timely manner. In this case, what other method can catch all errors globally and get the details?

Hijack native method

Look at an example:

const nativeAddEventListener = EventTarget.prototype.addEventListener; // Save the native methods first.
EventTarget.prototype.addEventListener = function (type, func, options) { // Override native methods.
    const wrappedFunc = function (. args) { // Wrap the callback around a try catch
        try { 
			return func.apply(this, args);
		} catch (e) {
			const errorObj = {
                ...
                error_name: e.name || ' '.error_msg: e.message || ' '.error_stack: e.stack || (e.error && e.error.stack),
				error_native: e,
                ...
			};
            // errorObj can be processed as a whole.}}return nativeAddEventListener.call(this, type, wrappedFunc, options); // Call the native method to ensure that addEventListener executes correctly
}
Copy the code

We hijack the native addEventListener code by adding a try{}catch(){} layer to the addEventListener callback. Browsers do not cross-block try-catch exceptions, so we can get detailed error information. By doing this, we can get errors in all callback functions that listen for events. What about the other scenarios? Continue to hijack native methods.

In addition to event listening, interface requests are a frequent scenario in a front-end project. Following the code above, let’s hijack Ajax a little bit.


    if(! XMLHttpRequest) {return;
    }

    const nativeAjaxSend = XMLHttpRequest.prototype.send; // Save the native methods first.
    const nativeAjaxOpen = XMLHttpRequest.prototype.open;


    XMLHttpRequest.prototype.open = function (mothod, url, ... args) { // Hijack the open method to get the requested URL
        const xhrInstance = this; 
        xhrInstance._url = url;
        return nativeAjaxOpen.apply(this, [mothod, url].concat(args));
    }

    XMLHttpRequest.prototype.send = function (. args) { // Ajax requests are monitored in the send method.

        const oldCb = this.onreadystatechange;
        const oldErrorCb = this.onerror;
        const xhrInstance = this;

        xhrInstance.addEventListener('error'.function (e) { // The error caught here is a ProgressEvent. The value of e.target is an instance of XMLHttpRequest. An XMLHttpRequest error is raised when a network error (the Ajax was not sent) or when a cross-domain error occurs, and e.target. Status is 0 and e.target. StatusText is ".
          
            const errorObj = {
                ...
                error_msg: 'ajax filed'.error_stack: JSON.stringify({
                    status: e.target.status,
                    statusText: e.target.statusText
                }),
                error_native: e,
                ...
            }
          
            /* The errorObj can then be processed uniformly */
          
        });


        xhrInstance.addEventListener('abort'.function (e) { // Active ajax cancellation cases need to be flagged, otherwise false positives may be generated
            if (e.type === 'abort') { 
                xhrInstance._isAbort = true; }});this.onreadystatechange = function (. innerArgs) {
            if (xhrInstance.readyState === 4) {
                if(! xhrInstance._isAbort && xhrInstance.status ! = =200) { // Get an error message when the request failed
                   const errorObj = {
                        error_msg: JSON.stringify({
                            code: xhrInstance.status,
                            msg: xhrInstance.statusText,
                            url: xhrInstance._url
                        }),
                        error_stack: ' '.error_native: xhrInstance
                    };
                    
                    /* The errorObj can then be processed uniformly */
                    
                }
                
            }
            oldCb && oldCb.apply(this, innerArgs);
        }
        return nativeAjaxSend.apply(this, args); }}Copy the code

Some frameworks throw errors with the **console.error** method when we reference frameworks. We can hijack console.error to catch errors.

        const nativeConsoleError = window.console.error;
        window.console.error = function (. args) {
            args.forEach(item= > {
                if (typeDetect.isError(item)) {
                   ...
                } else{... }}); nativeConsoleError.apply(this, args);
        }
Copy the code

There are many native methods, such as Fetch, setTimeout, etc. I’m not going to list them all here. But using the hijack native approach to cover all scenarios is difficult.

How does the front-end framework catch errors

Let’s take a look at how React and Vue address error catching.

Error capture in React

Prior to ActV16, you could use unstable_handleError to handle caught errors. After Reactv16, use componentDidCatch to handle caught errors. To catch errors globally, you can wrap a layer of components in the outermost layer and catch error messages in componentDidCatch. Please refer to the official documentation: reactjs.org/blog/2017/0…

In React, errors are thrown. While writing this article, I ran into a problem. If I hijacked the addEventListener before loading the React code, react would not work properly, but there were no errors. React has its own event system. Could this have something to do with it? I haven’t studied React source code before. I did some rough debugging and found no problem. We’ll look into it later.

Error capture in Vue

Vue source code, in the execution of key functions (such as hook functions, etc.), add try{}catch(){}, in the cacth to deal with the caught error. Look at the source code below.

.// Vue source code fragment
function callHook (vm, hook) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget();
  var handlers = vm.$options[hook];
  if (handlers) {
    for (var i = 0, j = handlers.length; i < j; i++) {
      try {
        handlers[i].call(vm);
      } catch (e) {
        handleError(e, vm, (hook + " hook")); }}}if (vm._hasHookEvent) {
    vm.$emit('hook:'+ hook); } popTarget(); }... function globalHandleError (err, vm, info) {if (config.errorHandler) {
    try {
      return config.errorHandler.call(null, err, vm, info)
    } catch (e) {
      logError(e, null.'config.errorHandler');
    }
  }
  logError(err, vm, info);
}

function logError (err, vm, info) {
  {
    warn(("Error in " + info + ": \" " + (err.toString()) + "\" "), vm);
  }
  /* istanbul ignore else */
  if ((inBrowser || inWeex) && typeof console! = ='undefined') {
    console.error(err);
  } else {
    throw err
  }
}
Copy the code

Vue.config.errorHandler ‘is provided in Vue to handle the errors caught.

// err: the error object caught.
// vm: error VueComponent.
// info: VUe-specific error information, such as the lifecycle hook where the error occurred
Vue.config.errorHandler = function (err, vm, info) {}
Copy the code

ErrorHandler if the developer has not configured vue.config. errorHandler, the errors caught will be output as console.error.

Report the error

Once you catch an error, how do you report it? The most common and easiest way to do this is through . The code is simple and there are no cross-domain annoyances.

function logError(error){
    var img = new Image();
    img.onload = img.onerror = function(){
        img = null;
    }
    img.src = `${address}?${processErrorParam(error)}`;
}
Copy the code

When a large amount of data is reported, you can use POST to report data.

Error reporting is actually a complex project, involving reporting strategy, reporting classification and so on. Especially when the project services are complex, you should pay more attention to the quality of the report to avoid affecting the normal operation of the service functions. Code processed using the packaging tool often also needs to be located in conjunction with sourceMap. I won’t do that in this article.

Write in the back

It is a complex and huge project to establish a complete and usable front-end error monitoring system. But the project is often necessary. This article focuses on the details of errors that you may not have noticed, and how to catch errors in your pages. The code for the hijacking native method section can be found at github.com/CoyPan/Fec.

As expected.