前端常见异常及捕获方法 | 青训营笔记

288 阅读7分钟

这是我参与「第四届青训营 」笔记创作活动的的第 11 天。

前言

相信大家在日常使用软件或访问网站的时候,总会碰到各种各样的异常情况,像按键无法点击、页面白屏、程序卡顿,这都是我们不愿意遇到的场景。对于前端来说,程序异常十分直观地影响了用户的体验,所以异常处理在程序设计中的重要性是毋庸置疑的。接下来让我们一起来了解前端不同场景下常见的的异常及如何捕获和处理。

前端常见异常

  1. 程序卡顿或崩溃
  2. 跨域
  3. Iframe 异常
  4. 异步错误和 Promise 异常
  5. 静态资源加载异常
  6. 网络请求异常
  7. JS 中的 Error 错误
  8. DOMException

JavaScript 中的错误类型

  • Error 普通异常,通常与throw语句和try/catch语句一起使用,利用异常实例对象的 name 属性可以声明或了解异常的类型,利用 message 属性可以设置和读取异常的详细信息。

  • EvalError Eval 函数执行异常

  • SyntaxError 语法错误

  • RangeError 是包含在变量或参数中的数值超出有效范围,或调用栈溢出时发生的错误,例如new Array(-1)递归调用超出最大栈内存

  • ReferenceError 引用错误,即在作用域中访问不存在的变量(未定义的变量)时抛出。

  • TypeError 类型错误,当一个值的类型发生错误,或尝试对变量的值进行不合理的操作时抛出,例如给 const 变量重复赋值,访问 undefined、null 类型的值中的属性,对非函数类型的变量进行函数调用。

  • URIError 以错误的方式使用全局 URI 处理函数而产生的错误,例如当向全局 URI 处理函数传递一个不合法的 URI 时,URIError 错误会被抛出。

  • AggregateError 当多个错误需要包装在一个错误中时,该对象表示这个包装起来的错误。

  • InternalError 当 JavaScript 引擎出现内部错误时将会抛出(非标准的特性,避免在生产环境中使用)。例如too many switch cases (过多case子句)

异常捕获

try-catch 语句

try-catch 语句 ECMA-262 第 3 版中引入了 try-catch 语句,作为 JavaScript 中处理异常的一种标准方式。如果 try 块中的任何代码发生了错误,就会立即退出当前代码块的执行,直接执行 catch 块中的代码。 但是,try-catch只能捕获到同步的运行时错误,无法处理异步错误、代码语法错误等场景,本文后续内容会根据不同异常场景详细分析处理方案。

try {
    // 可能出现错误的代码
} catch (error) {
    // 错误处理
}

finally 语句 该语句在try-catch语句后是可选的,如果使用了finally语句,则无论 try-catch 语句块中包含什么代码(甚至 return 语句),都不会阻止 finally 子句的执行。

function fn() {
    try {
        let a = a; // ReferenceError
        return a;
    } catch (error) {
        return error;
    } finally {
        console.log("即便之前的代码有return,但这句话还是会输出");
    }
}

throw 抛出异常

throw 语句通常用于主动抛出自定义的错误。

function fn() {
    throw new Error("主动抛出的异常");
}

try {
    fn();
} catch (error) {
    console.log(error);
    console.log(error.name); // Error
    console.log(error.message); // '主动抛出的异常'
}

全局异常捕获 window.onerror

window.onerror = function (message, source, lineno, colno, error) {};

window.onerror 可以捕获异步错误,无法捕获语法错误,无法处理网络请求的异常

window.onerror 函数只有在返回 true 的时候,异常才不会向上抛出(浏览器控制台报错),否则即使是知道异常的发生,控制台还是会显示 Uncaught Error: xxxxx

  • message:错误消息
  • source:引发错误的脚本的 URL
  • lineno:发生错误的行号
  • colno:发生错误的行的列号
  • error:错误对象

全局事件监听 window.addEventListener("error")

window.addEventListener("error", (error) => {
    console.log("捕获到异常:", error);
});

回调函数中的参数 error 是 Event 的实例对象,包括了错误的各种信息。

  • error.message 错误的描述信息
  • error.filename 发生错误的脚本文件的文件名
  • error.error 发生错误时所抛出的 Error 对象
  • error.target 触发错误的目标元素

window.onerror 和 window.addEventListener('error')的区别

  1. 首先是事件处理器和事件监听器的区别。事件处理器只能声明一次,后续的声明会覆盖之前的声明。而监听器则可以绑定多个回调函数。

  2. 资源( img 或 script )加载失败时,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的 onerror 属性绑定的处理函数。但这些 error 事件不会向上冒泡到 window。不过,这些 error 事件能被 window.addEventListener('error') 捕获。也就是说,面对资源加载失败的错误,只能用 window.addEventListerner('error')处理,window.onerror 无效。

  3. window.addEventListener('error') 会比 window.onerror 先触发。

异常处理

了解了以上异常捕获的方式后,在开发过程中,我们可以给容易出错的代码加上try-catch语句进行处理,对于全局的错误捕获,推荐使用window.addEventListener('error')进行捕获,但是为了兼容性,我们可以加上window.onerror方式,三种方法同时使用,

当我们捕获到异常后,我们需要考虑以下几点:

  • 错误是否致命,会不会导致程序崩溃,或引起其他错误
  • 后续的代码逻辑能否正常执行,用户能否继续操作
  • 是否需要将异常信息反馈给用户,并提示引导用户处理
  • 是否需要将异常上报到服务端,上报哪些信息

不同场景下的异常处理方案

静态资源加载异常

方法一:利用标签的 onerror 属性绑定函数进行捕获

<script>
    function errorHandler(error) {
        console.log("捕获到异常", error);
    }
</script>
<script src="./test.js" onerror="errorHandler(this)"></script>
<link rel="stylesheet" href="./style.css" onerror="errorHandler(this)" />

方法二:window.addEventListener('error')使用捕获阶段

由于网络请求异常不会事件冒泡,所以必须在事件的捕获阶段将错误就行捕捉,这种方式虽然可以捕获网络请求的异常,但是无法判断 HTTP 的状态是 404 或者 500 等,所以还得配合控制台的网络工具和服务端日志就行排查分析才可以。

<script>
window.addEventListener(
    "error",
    (error) => {
        console.log("捕获到异常:", error);
    },
    true // 事件捕获阶段
);
</script>
<img src="./xxx.png">

全局捕获 Promise 异常

我们可以使用 Promise 对象的 catch 方法十分方便的捕获到异步的错误,但是有没有什么方法可以全局捕获 Promise 异常呢? 没有使用 catch方法的 Promise 中的异常不能被 try-catchwindow.onerror 捕获,这时候我们就需要监听 unhandledrejection 来进行错误的捕获。

window.addEventListener("unhandledrejection", function (e) {
    e.preventDefault(); // 阻止浏览器控制台报错
    console.log("错误的原因", e.reason);
    console.log("发生错误Promise对象", e.promise);
    return true;
});

Promise.reject("promise error");

new Promise((resolve, reject) => {
    reject("promise error");
});

new Promise((resolve) => {
    resolve();
}).then(() => {
    throw "promise error";
});

Vue 框架中的异常处理

Vue 提供了一个 API 帮助我们就行异常捕获。

Vue.config.errorHandler = function (err, vm, info) {
    let {
        message, // 异常信息
        name, // 异常名称
        script, // 异常脚本url
        line, // 异常行号
        column, // 异常列号
        stack, // 异常堆栈信息
    } = err;

    // vm为抛出异常的 Vue 实例
    // info为 Vue 特定的错误信息,比如错误所在的生命周期钩子
};

axios 请求异常

方法一:利用 promise 对象的 catch 方法进行捕获

axios
    .get("http://localhost/api")
    .then((res) => console.log("接口请求成功", res))
    .catch((err) => console.log("接口请求出错", err));

方法二:使用 axios 的拦截器

axios.interceptors.response.use(
    (response) => {},
    (error) => {
        // 异常处理
        // ......
        return Promise.reject(error);
    }
);

DOMException

DOMException 是 W3C DOM 核心对象。DOMException 接口代表调用方法或访问 Web API 属性时发生的异常事件。例如试图创建一个无效的 DOM, 或通过一个不存在的节点作为参数节点操作方法。

DOMException - Web API 接口参考 | MDN (mozilla.org)

待补充

  • iframe 异常
  • 程序卡顿和崩溃

写在最后

看到这里,相信大家对前端的异常的捕获和处理也有了一定的了解,如果对你有帮助,留个赞再走吧~ 1B5DDB63.png

如果文中有任何错误,还请各位大佬们指出。