面试官系系列:说说前端异处理吧

299 阅读4分钟

前言

关于前端异常处理,东西不多也不复杂都是老生常谈。但是纸上得来终觉浅,要知此事须躬行。来,请随我一起看下去 (ง •_•)ง。

关于常见错误

首先通过一行代码查看浏览器下都有哪些错误类型

Object.getOwnPropertyNames(window).filter(err => err.endsWith ('Error'))

可以得到所有的Error类型。

1.png

对于我们开发常见的有以下几个

  1. RangeError
  2. ReferenceError
  3. SyntaxError(一般不需要我们处理)
  4. TypeError

1:RangeError

RangeError对象标明一个错误,当一个值不在其所允许的范围或者集合中。

var arr = []
a.length = -1 // Uncaught RangeError: Invalid array length

2:ReferenceError

ReferenceError(引用错误)对象代表当一个不存在的变量被引用时发生的错误。

1:变量没有被声明

b.length // Uncaught ReferenceError: b is not defined

2:错误的作用域

function numbers () {
  var num1 = 2,
      num2 = 3;
  return num1 + num2;
}

console.log(num1); // ReferenceError num1 is not defined.

3:SyntaxError

SyntaxError 对象代表尝试解析语法上不合法的代码的错误。

var a b c // Uncaught SyntaxError: Unexpected identifier

对于SyntaxError错误在我们编写代码阶段就已经被抛出,不需要我们再做代码处理

4:TypeError

TypeError(类型错误)对象用来表示值的类型非预期类型时发生的错误。

// undefined and null cases on which the substring method won't work
var foo = undefined;
foo.substring(1); // TypeError: foo is undefined

var foo = null;
foo.substring(1); // TypeError: foo is null

// Certain methods might require a specific type
var foo = {}
Symbol.keyFor(foo); // TypeError: foo is not a symbol

var foo = 'bar'
Object.create(foo); // TypeError: "foo" is not an object or null

以上我们罗列出了代码中的常见错误问题,接下去我们探讨一下如何捕获问题

关于错误捕获

1:老生常谈的 try catch

先看例子

// 同步错误
try {
  var arr = []
  arr.length = -1
} catch (err) {
  console.log('err', err) // err RangeError: Invalid array length
}

try {
  b.length
} catch (err) {
  console.log('err', err) // err ReferenceError: b is not defined
}

try {
  var foo = undefined;
  foo.substring(1);
} catch (err) {
  console.log('err', err) // err TypeError: Cannot read properties of undefined (reading 'substring')
}

console.log('start')
try {
  console.log('run')
  b.length
} catch (err) {
  console.log('err', err)
}
console.log('end')
start
run
err ReferenceError: b is not defined
end

try块里的同步错误都被正确捕获并且不影响代码正常执行

// 异步错误
var a = null
console.log('start')
try {
  setTimeout(() => {
      console.log('我是一个异步错误:', a.splice(','))
  });
} catch (err) {
  console.log('err', err)
}
console.log('end')

2.png

看来异步是搞不定了!!!

结论1:try块可以捕获同步错误且不影响代码正常执行流,但异步无能为力

2:window.onerror

// 异步错误
/**
 * @param {String}  msg    错误信息
 * @param {String}  url    出错文件
 * @param {Number}  row    行号
 * @param {Number}  col    列号
 * @param {Object}  error  错误详细信息
 */
console.log('start')
setTimeout(() => {
  let a = null
  console.log('我是一个异步错误:', a.splice(','))
});
console.log('end')
window.onerror = function (msg, url, row, col, error) {
  console.log('我捕获了一个异步错误', {
    msg,  url,  row, col, error
  })
  return true;
};

3.png

运行上面的代码我们可以看出window.onerror貌似可以捕捉到异步错误,而且报错信息很全

// 来同步也试一个
let a = null
window.onerror = function (msg, url, row, col, error) {
  console.log('我捕获一个同步错误:', {
    msg,  url,  row, col, error
  })
  return true;
};
console.log('我是一个同步错误', a.splice(','))

4.png

从上方可以看出window.onerror只能捕获异步错误,同步错误无能为力

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>
<script type="text/javascript">
    window.onerror = function(msg, url, row, col, error) {
        console.log('我捕获一个引用资源错误:', {
            msg,  url,  row, col, error
        })
        return true
    };
</script>
<script src="./outSideSource.js"></script>
</html>
// outSideSource.js
var arr = []
arr.length = -1

5.png 从上方可以看出window.onerror可以捕获一个引用资源的报错,但如果是一个异步的资源可以吗?

// outSideSource.js
setTimeout(() => {
    var arr = []
    arr.length = -1
}, 5000)

6.png

假设我们引用的资源不存在是否会被捕获?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>
<script type="text/javascript">
    window.onerror = function(msg, url, row, col, error) {
        console.log('我捕获一个引用资源错误:', {
            msg,  url,  row, col, error
        })
        return true
    };
</script>
<script src="./abcd.js"></script>
</html>

7.png

可以看到对于不存在的资源确实无能为力

结论2:window.onerror可以捕获异步错误,引用资源内部的错误。但同步的无能为力,资源不存在也无能为力。

3:window.addEventListener

具体参见MDN

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

</body>
<script type="text/javascript">
    window.addEventListener('error', (error) => { console.log('我捕获一个引用资源错误:', error); }, true)
</script>
<script src="./abcd.js"></script>
</html>

8.png

可以看出对于资源不存在的情况也可以捕获

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<img src="./abcd.js" />
</body>
<script type="text/javascript">
    window.addEventListener('error', (error) => {
        console.log('我捕获一个引用资源错误:', error);
    }, true)
</script>
</html>

9.png

以上可以看出Dom图片资源错误也可以捕捉,非常棒!

window.addEventListener("unhandledrejection", function(e){
  e.preventDefault()
  console.log('我知道 promise 的错误了:', e.reason);
  return true;
});
Promise.reject('promise error1');
new Promise((resolve, reject) => {
  reject('promise error2');
});
new Promise((resolve) => {
  resolve();
}).then(() => {
  throw 'promise error3'
});

10.png

以上可以看出unhandledrejection并不能完全捕获reject

window.addEventListener("unhandledrejection", function(e){
  e.preventDefault()
  console.log('我知道 promise 的错误了:', e.reason);
  return true;
});
Promise.reject('promise error1').catch((e) => console.log(e));
new Promise((resolve, reject) => {
  reject('promise error2');
}).catch((e) => console.log(e));
new Promise((resolve) => {
  resolve();
}).then(() => {
  throw 'promise error3'
});

11.png 结论3:window.addEventListener这个api很是强大,只有promise的部分reject不能正确捕获。那么我们要如何处理?其实在我们实际开发中直接使用promise用的比较少,多是用async await来做异步任务,所以如果代码中出现了promise建议还是需要写上catch进行捕获,虽然确实是麻烦一些。

4:cdn引用问题

先看看MDN的解释

13.png 这一块,嗯还没想好如何处理,暂且先做记录吧

总结

捕获方法同步异步引用资源资源不存在Dom相关错误promise
try_catch
window.onerror
window.addEventListener_error
window.addEventListener_unhandledrejection✔(部分)

推荐好文