How to search for bugs on the front end: 4 main stages


In this article I will consider the issue of identifying and handling errors that occur on the frontend (browser or web-view).

On the frontend, JS code is executed in the browser. JavaScript is not a compiled language, so there is always a chance of a performance error when using the program directly. An execution error blocks the code located after the place of the error, and users of the program run the risk of remaining with a non-functional application screen that can only be reloaded or closed. But there are methods for detecting errors and their safe accompaniment, to avoid such situations.

1. JavaScript Tools


Try / catch blocks


Functions that may fail may be wrapped in try / catch blocks. First, the program tries to execute the code in the try block. If for some reason the code execution is broken, the program goes into the catch block, where three parameters are available:

  • name - standardized name of the error;
  • message - message about the details of the error;
  • stack - the current call stack in which the error occurred.

I.e:

try {

   callFunc();

} catch (e) {

   console.log(e.name) // ReferenceError

   console.log(e.message) // callFunc is not defined

   console.log(e.stack) // ReferenceError: callFunc is not defined at window.onload

}

For the developer, the main thing is that the program will be able to continue execution after the catch block. Thus, user interaction will not be interrupted.

Using the try / catch block, you can also cause your own errors, for example, when checking data:

const user = {

  name : «Mike»

};

try {

   if (!user.age) throw new SyntaxError(«User age is absent!»);

} catch (e) {

   console.log(e.name) // SyntaxError

   console.log(e.message) // User age is absent!

   console.log(e.stack) // SyntaxError: User age is absent! at window.onload

}

Finally, you can expand this statement with one more block - finally, which is always executed: both in the case where there was no error in try, and in case the control passed to the catch block:

try {

   callFunc();

} catch (e) {

   console.log(e)

} finally {

   ...

}

Sometimes they use the try / finally statement (without catch) so that you can continue to use the code without handling specific errors.

Window.onerror event


It is often useful to know that the script on the page has broken, even if the program has broken and the user session has ended unsuccessfully. Usually this information is then used in error logging systems.

The global window object has an onerror event ( use it carefully: the implementation may differ in different browsers! ):

window.onerror = function(message, url, line, col, error) {

   console.log(`${message}\n  ${line}:${col}  ${url}`);

};

If you place this code at the beginning of the script or load it in a separate script in the first place, then with any error below, the developer will be available detailed information about it.

However, full information is only available for scripts that have been downloaded from the same domain. If the broken script is loaded from another domain, window.onerror will work, but there will be no details of the error.

Framework components


Some JS frameworks ( React , Vue) offer their own error handling solutions. For example, React will be able to draw a special layout at the location of the block in which the error occurred:

class ErrorBoundary extends React.Component {

   constructor(props) {

       super(props);

       this.state = { hasError: false };

   }

   static getDerivedStateFromError(error) {

       //    ,      UI.

       return { hasError: true };

   }

   componentDidCatch(error, errorInfo) {

       //           

       logErrorToMyService(error, errorInfo);

   }

   render() {

       if (this.state.hasError) {

           //    UI  

           return <h1>-   .</h1>;

       }

       return this.props.children;

   }

}

<ErrorBoundary>

   <MyWidget />

</ErrorBoundary>

In fact, the React component is wrapped in a special component that handles errors. This is similar to wrapping functions with a try / catch construct.

2. Project assembly tools


Modern JS scripts are usually made transpiled. That is, development is carried out using the latest ES standards. And then the code of the developer using the project builder (such as Webpack) is converted into code that will be guaranteed to work in the selected number of browsers.

At the build stage, the code is checked for correct syntax. An open bracket or an incorrect designation of a class field will immediately cause an error during assembly, and the bundle simply will not be assembled. The developer will have to immediately fix such errors in order to continue working.

Also, the collector may suggest that some pieces of code are not used when executing the program. Perhaps this will prompt the developer to think about a deeper study of the code, which may indirectly affect the identification of new errors.

3. Testing


Another way to prevent errors in the code is to test it. There are tools in the frontend for the effective use of unit tests. Typically, frameworks such as Jest, Karma, Mocha, Jasmine are used. Together with test frameworks, they often use extensions such as Enzyme, React Testing Library, Sinon, and others, which allow enriching tests with the help of mocha, spy functions and other tools.

When searching for errors, tests are primarily useful for loading a variety of data that can lead to execution errors. So, the following code will pass syntax validation and work as expected:

const func = (data) => {

   return JSON.parse(data)

}

func('{«a»:1}')

However, it will break if you give it the wrong value:

func() // Uncaught SyntaxError: Unexpected token u in JSON at position 0.


This code also passes validation during assembly:

const obj = {

   outer : {

       last : 9

   }

}

if (obj.outer.inner.last) {

   console.log(«SUCCESS»)

}

However, it will also break during execution. After testing, the developer will probably do additional checks:

if (obj.outer?.inner?.last) {

   console.log(«SUCCESS»)

}

Often, such errors occur when receiving data from the server (for example, with an AJAX request) with their subsequent analysis. Testing the code allows you to identify and eliminate in advance cases where the code may break during execution in the client’s browser.

4. Logging


Suppose we have taken all possible measures to prevent errors during the development and assembly of the project. However, errors can still infiltrate productive code. We need to somehow find out about their presence and take immediate corrective measures. Asking users to open a browser console and take screenshots is not the best option. Therefore, it’s good to connect error logging to the project.

The meaning is simple: for each window.onerror event or each transition of code execution to the catch block, a simple AJAX request is made to a specially allocated server address, in the body of which error information is placed. Next, you will need a tool that will quickly notify tech support and developers about the presence of new errors and allow you to work effectively with them. The most popular of these frontend tools is Sentry.

Sentry logging system allows you to collect, group, present errors in real time. There are assemblies for different languages, including JavaScript. The project provides paid access with advanced features for business, however, you can try its main features on a free test account.

Sentry can be connected either directly in the HTML file, or in components executed on one of the popular frameworks: React, Vue, Angular, Ember and others.

To connect the logging capabilities directly in the browser in the section, load the script:

<script

   src=«https://browser.sentry-cdn.com/5.13.0/bundle.min.js»

   integrity=«sha384-ePH2Cp6F+/PJbfhDWeQuXujAbpil3zowccx6grtsxOals4qYqJzCjeIa7W2UqunJ»

   crossorigin="anonymous"></script>


Next, in the JS code, we initialize:

Sentry.init({

   dsn: 'https://<your account key here>@sentry.io/<your project id here>'

});

All. If and when an error occurs in the code below this line, Sentry will log it. Logs will be recorded even when the error occurred due to the fault of scripts from other domains:



Sentry has ample opportunity to analyze an array of error messages and configure notifications. It is also possible to group error logs by the releases of your product:

Sentry.init({

   dsn: 'https://<your account key here>@sentry.io/<your project id here>',

   release: '2020.03.06.1'

});

Using Sentry, statistics can be used to transfer the context of an error, for example, customer information, fingerprint, error level (fatal, error, warning, info, debug), and tag.

It is possible to record user events in statistics. For example, you can set tracking to change the size of the browser window or make an AJAX request. Sentry also has its own widget with a feedback window, which can be shown to the user in case of error. This will provide additional information to investigate the circumstances of the error.

To deploy Sentry along with the frameworks, just install the package and connect:

# Using yarn

yarn add @sentry/browser

# Using npm

npm install @sentry/browser


We do initialization in the main script of the project (for React and Angular):

import * as Sentry from «@sentry/browser»;

Sentry.init({ dsn: 'https://<your account key here>@sentry.io/<your project id here>' });


For Vue and Ember, we pass another required configuration line:

# Vue

Sentry.init({

   dsn: '<your account key here>@sentry.io/<your project id here>',

   integrations: [new Integrations.Vue({Vue, attachProps: true})],

});

# Ember

Sentry.init({

   dsn: '<your account key here>@sentry.io/<your project id here>',

   integrations: [new Integrations.Ember()]

});

The integrations package is installed separately:

# Using yarn

yarn add @sentry/integrations

# Using npm

npm install @sentry/integrations

To prevent conflicts and duplication of information when connecting multiple scripts in one project, Sentry allows you to create a separate client for each logging initialization:

import { BrowserClient } from «@sentry/browser»;

const client = new BrowserClient({

   dsn: '<your account key here>@sentry.io/<your project id here>',

});

client.captureException(new Error('example'));

The project website has detailed documentation with examples of use: https://docs.sentry.io .

This article was prepared with the support of the Mail.ru Cloud Solutions cloud platform .

What else to read on the topic:

  1. Reactable React Components: How to Stop Writing the Same .
  2. How to avoid errors when developing on React .
  3. - .

All Articles