前端中 try-catch 捕获不到哪些异常和错误

蚊子前端博客
发布于 2021-01-27 14:35
我们经常会使用try-catch来捕获异常和错误,但又有哪些错误是try-catch捕获不到的呢?

在开发过程中,我们的目标是 0error,0warning。

0error0waring-蚊子的前端博客

但有很多因素并不是我们可控的,为了避免某块代码的错误,影响到其他模块或者整体代码的运行,我们经常会使用try-catch模块来主动捕获一些异常或者错误。

比如我们在获取 url 中的参数后,对其进行 JSON 解析,这里就要用try-catch包裹一下,因为我们不能保证获取到的参数一定是可以正常解析的:

COPYJAVASCRIPT

const addparams = getQueryString('_addparams'); if (addparams) { try { const { openid, token } = JSON.parse(addparams); console.log(openid, token); } catch (err) { console.error(err); } }

用户在复制链接的过程中,有可能会有意无意地复制不完全,导致整个参数不完整,JSON.parse无法解析不完整的 json string。为了避免因数据不完整造成的 JSON 解析错误,我们可以将其用try-catch包括起来。

1. try-catch 不能捕获哪些错误

我们经常会使用try-catch模块来主动捕获一些异常或者错误,避免此块的代码影响到其他模块或者整体代码的运行。但有些情况,try-catch 并不能捕获到代码中的异常!

1.1 跨域的错误

当我们使用 xhr 请求接口,若接口不支持跨域时,浏览器会在控制台提示错误:

COPYJAVASCRIPT

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

跨域错误无法被try-catch捕获-蚊子的前端博客

通过图片中我们可以看到,请求接口时产生了跨域错误,但并没有进入到catch中。

之前我在做 canvas 合成图片时,某些图片的域名不支持跨域,本来想的是先直接请求图片,如果图片跨域了,则请求后台接口,后台接口将该图片转为 base64。但在测试的过程中发现,这个跨域的错误,一定会展示出来。可是,我本来就不想展示给调用方,因为我已经设置了兜底的方案了。但用 try-catch 是兜不住这个错误的。

当然,这并不是说前端不知道产生了跨域的错误,我们通过 xhr.onerror 的监听,是可以知道 xhr 请求产生了请求错误的。

1.2 异步错误

若 try 中异步的模块产生了错误,catch 也是捕获不到的,例如:

COPYJAVASCRIPT

// 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); }

异步的错误无法被try-catch捕获-蚊子的前端博客

通过图片中的运行结果可以看到,这两种代码,均没有进入到 catch 模块中。

那么我们应该怎么捕获这种异步的错误呢?答案就是把 try-catch 放到异步代码的里面。

COPYJAVASCRIPT

// 将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() .then(() => { throw new Error('error in Promise.then'); }) .catch((err) => { console.error('Promise.catch error', err); });

Promise 有一个很大的优势是,它自带着异常捕获方法catch(),在 then()方法产生错误导致代码无法运行时,会自动进入到 catch()方法中。因此建议写 Promise 时,都把 catch()写上,否则未捕获的异常,就会冒泡到全局。

1.3 async-await 的异常如何捕获

async-await的语法糖,可以让我们像写同步代码一样写 Promise,那么在 async-await 中如何捕获异常呢?

这里我们通常就会使用try-catch来捕获异常了。

COPYJAVASCRIPT

const request = async () => { try { const { code, data } = await somethingThatReturnsAPromise(); } catch (err) { console.error('request error', err); } };

当 somethingThatReturnsAPromise()方法产生 reject 的异常时,就会被 catch 捕获到。

当然,async-await 还有一种捕获异常的方式,在通过 await 返回正确数据时,还可以顺带写上catch()捕获异常,当 somethingThatReturnsAPromise()方法异常时,就会自动进入到 catch()方法中:

COPYJAVASCRIPT

const request = async () => { try { const { code, data } = await somethingThatReturnsAPromise().catch((err) => console.error('catch error', err)); } catch (err) { console.error('request error', err); } };

但这种捕获异常后,外层的 catch()方法就捕获不到异常了,不再继续向外层冒泡了。正确的做法是,底层模块产生的错误,应当直接抛出给业务层,让业务层决定这个错误怎么处理,而不是直接吞掉。

1.4 多层 try-catch

多层 try-catch 时,会被最内层的 catch()方法捕获到,然后就不再向外层冒泡:

COPYJAVASCRIPT

try { try { throw new Error('error'); } catch (err) { console.error('内层的catch', err); // 内层的catch Error: error } } catch (err) { console.error('最外层的catch', error); }

接下来我们理解下 js 中出现的错误类型。

2. 原生的错误类型

在了解使用try-catch之前,我们先来了解下 js 中有哪些个原生的错误类型。

js 代码在运行时可能产生的错误错误共有 6 种类型:

  • 语法错误(SyntaxError);

  • 类型错误(TypeError);

  • 范围错误(RangeError);

  • eval 错误(EvalError);

  • 引用错误(ReferenceError);

  • URI 错误(URIError);

这些错误类型都继承自Error类。

2.1 语法错误(SyntaxError)

语法错误,通常是开发者在开发过程,代码语句写的有问题,浏览器无法对其进行解析:

COPYJAVASCRIPT

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

2.2 类型错误(TypeError)

类型错误通常会出现在两种情况:

  • 操作符使用在了不适当的类型变量上,例如对数字类型使用 concat 操作;

  • 操作的变量遇到不可预期的 null 或者 undefined 值:

COPYJAVASCRIPT

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

在编写一些方法供给其他模块调用时,当在检查到参数传入为空或者 null 等空置时,可以抛出TypeError的错误。

2.3 范围错误(RangeError)

该错误通常是因为传入的参数,超出了规定的范围。例如toFixed()方法可以接受 0-100 范围内的数值,当超过这个范围时,就会抛出该错误。

COPYJAVASCRIPT

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

2.4 eval 错误(EvalError)

这种错误一般很少会遇到,因为使用 eval 操作时,即使不正当的错误,也会抛出其他类型的错误。

COPYJAVASCRIPT

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

2.5 引用错误(ReferenceError)

引用错误表示师徒访问一个未经声明的变量:

COPYJAVASCRIPT

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

2.6 URI 错误(URIError)

该错误通常是一些操作 uri 函数抛出的错误,主要包括:encodeURI(), decodeURI(), encodeURIComponent(), decodeURIComponent(), escape(), unescape()。

COPYJAVASCRIPT

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

3. 自定义错误类型

对于稍微大点的模块,我们想自定义一些错误类型,通过这些错误类型,就能看出是某个模块抛出的错误。该怎么写呢?

我们自定义的错误类型也是要继承自Error类的,实现起来非常简单:

COPYJAVASCRIPT

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. 总结

前端中还有很多种产生错误的方式的,我们平时就要注意避免这些错误。我们接下来也可以从错误监控的角度来分析下,如何来监控页面中出现的错误和错误类型。

标签:
阅读(2534)

公众号:

qrcode

微信公众号:前端小茶馆

公众号:

qrcode

微信公众号:前端小茶馆