During development, our goal is 0error, 0warning.

But there are many factors beyond our control, and to avoid errors in one piece of code that affect other modules or the overall code, we often use the try-catch module to actively catch some exceptions or errors.

For example, if we get the parameters in the url and parse them in JSON, we need to wrap them in try-catch because we can’t guarantee that the parameters we get will be parsed properly.

1
2
3
4
5
6
7
8
9
const addparams = getQueryString('_addparams');
if (addparams) {
  try {
    const { openid, token } = JSON.parse(addparams);
    console.log(openid, token);
  } catch (err) {
    console.error(err);
  }
}

It is possible that the user may intentionally or unintentionally copy the link incompletely, resulting in an incomplete parameter. JSON.parse cannot parse incomplete json string.

To avoid JSON parsing errors caused by incomplete data, we can include it with try-catch.

1. What errors can’t be caught by try-catch

We often use the try-catch module to actively catch some exceptions or errors, so that this block of code does not affect other modules or the overall code. But there are some cases where try-catch does not catch an exception in the code!

1.1 Cross-domain errors

When we use xhr to request an interface, the browser will prompt an error in the console if the interface does not support cross-domain.

1
Access to XMLHttpRequest at 'https://xxxxxxx.qq.com/qq/userInfo' from origin 'https://www.xiabingbao.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource

sobyte

As we can see in the image, the request for the interface is generating a cross domain error, but it’s not going to catch.

Previously, when I was doing canvas compositing, the domain name of some images did not support cross-domain, so I wanted to request the image directly first, and if the image was cross-domain, then request the backend interface, which would convert the image to base64, but in the process of testing, I found that this cross-domain error would definitely be shown. However, I didn’t want to show it to the caller in the first place, because I had already set up a tuck-in solution. But you can’t trot out the error with try-catch.

Of course, this doesn’t mean that the front-end doesn’t know that a cross-domain error was generated. We can know that the xhr request generated a request error by listening to xhr.onerror.

1.2 Asynchronous errors

If an error is generated by an asynchronous module in try, catch will not catch it, for example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// setTimeout中的错误
try {
  setTimeout(function () {
    throw new Error('error in setTimeout'); // 200ms后会把异常抛出到全局
  }, 200);
} catch (err) {
  console.error('catch error', err); // 不会执行
}

// Promise中的错误
try {
  Promise.resolve().then(() => {
    throw new Error('error in Promise.then');
  });
} catch (err) {
  console.error('catch error', err);
}

sobyte

As you can see by the runtime results in the image, both of these codes, do not go into the catch module.

So how should we catch this asynchronous error? The answer is to put the try-catch inside the asynchronous code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 将try-catch放到setTimeout内部
setTimeout(() => {
  try {
    throw new Error('error in setTimeout');
  } catch (err) {
    console.error('catch error', err);
  }
}, 200);

// 将try-catch放到then内部
Promise.resolve().then(() => {
  try {
    throw new Error('error in Promise.then');
  } catch (err) {
    console.error('catch error', err);
  }
});

// 使用Promse自带的catch捕获异常
Promise.resolve()

One great advantage of Promise is that it comes with an exception catch method catch() that automatically goes to the catch() method when the then() method generates an error that prevents the code from running. So it is recommended to write Promise with catch() on it, otherwise uncaught exceptions will bubble up to the global level.

1.3 How to catch exceptions in async-await

The syntactic sugar of async-await allows us to write Promise like synchronous code, so how do we catch exceptions in async-await?

Here we would normally use try-catch to catch exceptions.

1
2
3
4
5
6
7
const request = async () => {
  try {
    const { code, data } = await somethingThatReturnsAPromise();
  } catch (err) {
    console.error('request error', err);
  }
};

When the somethingThatReturnsAPromise() method throws a reject exception, it will be caught by catch.

Of course, async-await has another way of catching exceptions, by returning the correct data via await and writing catch() along with it, so that when the somethingThatReturnsAPromise() method throws an exception, it will automatically go to the catch() method.

1
2
3
4
5
6
7
const request = async () => {
  try {
    const { code, data } = await somethingThatReturnsAPromise().catch((err) => console.error('catch error', err));
  } catch (err) {
    console.error('request error', err);
  }
};

However, after this catching of the exception, the catch() method of the outer layer does not catch the exception and does not continue to bubble up to the outer layer. The correct approach is that errors generated by the underlying module should be thrown directly to the business layer to let the business layer decide what to do with the error, rather than just swallowing it.

1.4 Multi-layer try-catch

When multi-layer try-catch is used, it is caught by the innermost catch() method, and then it stops bubbling to the outer layer.

1
2
3
4
5
6
7
8
9
try {
  try {
    throw new Error('error');
  } catch (err) {
    console.error('内层的catch', err); // 内层的catch Error: error
  }
} catch (err) {
  console.error('最外层的catch', error);
}

Next, we understand the types of errors that occur in js.

2. Native error types

Before we get into using try-catch, let’s understand what are the native error types in js.

There are six types of errors that can be generated by js code at runtime.

  • SyntaxError.
  • TypeError.
  • rangeError.
  • evalError.
  • ReferenceError.
  • URI error.

These error types all inherit from the Error class.

2.1 SyntaxError

Syntax error, usually the developer in the development process, the code statement is written in a problem, the browser can not parse it.

1
2
const a=;
console.log(a); // Uncaught SyntaxError: Unexpected token ';'

2.2 TypeError

Type errors usually occur in two cases.

  • the operator is used on an improperly typed variable, such as a concat operation on a numeric type.
  • The operated variable encounters an unanticipated null or undefined value.
1
2
3
4
5
const obj = {};
obj.concat([1]); // Uncaught TypeError: obj.concat is not a function

const a = null;
a.nickname; // Uncaught TypeError: Cannot read property 'nickname' of null

When writing methods to be called by other modules, a TypeError error can be thrown when checking for vacant parameters such as null or null.

2.3 RangeError

This error is usually caused by passing in parameters that are out of the specified range. For example, the toFixed() method can accept values in the range 0-100, and when this range is exceeded, the error is thrown.

1
Math.PI.toFixed(105); // Uncaught RangeError: toFixed() digits argument must be between 0 and 100

2.4 evalError

This type of error is generally rarely encountered, because other types of errors are thrown when using eval operations, even if they are not justified.

1
2
new eval(); // Uncaught TypeError: eval is not a constructor
eval = 1234; // 正确执行

2.5 ReferenceError

A reference error indicates that the master or apprentice is accessing an undeclared variable.

1
console.log(nick); // Uncaught ReferenceError: nick is not defined

2.6 URI errors (URIError)

This error is usually thrown by some manipulation of uri functions, mainly: encodeURI(), decodeURI(), encodeURIComponent(), decodeURIComponent(), escape(), unescape().

1
2
decodeURIComponent('%'); // Uncaught URIError: URI malformed
decodeURIComponent('%23'); // # 正确执行

3. Customizing error types

For slightly larger modules, we want to customize some error types by which we can tell that a module is throwing an error. How should we write this?

Our custom error types are also inherited from the Error class, which is very simple to implement.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class FingerError extends Error {
  constructor(message) {
    super(message);
    this.name = 'FingerError'; // 该错误的名称
    Error.captureStackTrace(this, this.constructor); // 获取错误堆栈的信息
  }
}

const err = new FingerError('get name error');
console.error(err); // FingerError: get name error
err instanceof FingerError; // true

4. Summary

There are many ways to generate errors in the front end, and we should pay attention to avoid them. Next, we can also analyze how to monitor the errors and types of errors that occur in the page from the perspective of error monitoring.