资讯专栏INFORMATION COLUMN

详解JS错误处理:前端JS/Vue/React/Iframe/跨域/Node

张利勇 / 3045人阅读

摘要:错误上报机制发送数据因为请求本身也有可能会发生异常,而且有可能会引发跨域问题,一般情况下更推荐使用动态创建标签的形式进行上报。

js错误捕获

js错误的实质,也是发出一个事件,处理他

error实例对象

对象属性

message:错误提示信息

name:错误名称(非标准属性)宿主环境赋予

stack:错误的堆栈(非标准属性)宿主环境赋予

对象类型(7种)

SyntaxError对象是解析代码时发生的语法错误

ReferenceError对象是引用一个不存在的变量时发生的错误

RangeError对象是一个值超出有效范围时发生的错误(一是数组长度为负数,二是Number对象的方法参数超出范围,以及函数堆栈超过最大值)

TypeError对象是变量或参数不是预期类型时发生的错误:对字符串、布尔值、数值等原始类型的值使用new命令

URIError对象是 URI 相关函数的参数不正确时抛出的错误:使用函数不当

eval函数没有被正确执行时,会抛出EvalError错误 - 不再使用,为了代码兼容

自定义错误

function UserError(message) {
  this.message = message || "默认信息";
  this.name = "UserError";
}

UserError.prototype = new Error();
UserError.prototype.constructor = UserError;
new UserError("这是自定义的错误!");

Js运行时错误处理机制

try..catch…finally

范围:用来捕获任何类型的同步错误,可以捕获async / await的代码,但无法捕获promise、setTimeout、dom回调(eg:onclick点击回调)的代码,在回调函数里面写try…catch可以捕获,但包在外面不会捕获,无法捕获语法错误

异步不捕获原因:

async/await捕获原因:

window.onerror

范围:同步错误和异步错误都可以捕获,但无法捕获到静态资源异常,或者接口异常(网络请求异常不会事件冒泡,因此必须在捕获阶段将其捕捉到才行),无法捕获语法错误

原理:当 JS 运行时错误发生时,window 会触发一个 ErrorEvent 接口的 error 事件

参数

/**
* @param {String}  message    错误信息
* @param {String}  source    出错文件
* @param {Number}  lineno    行号
* @param {Number}  colno    列号

*/
window.onerror = function(message, source, lineno, colno, error) {
   console.log("捕获到异常:",{message, source, lineno, colno, error});
}
```

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

onerror 最好写在所有 JS 脚本的前面,否则有可能捕获不到错误;(捕获的是全局错误)

资源加载错误

window.addEventListener(一项资源(如图片或脚本)加载失败,加载资源的元素会触发一个 Event 接口的 error 事件,并执行该元素上的onerror() 处理函数,有浏览器兼容问题)

注意:只能捕获无法冒泡

window.addEventListener("error", (error) => {
    console.log("捕获到异常:", error);
}, true) // 一定要加true,捕获但不冒泡

Script error跨域的静态资源加载异常捕获(cdn文件等)

跨域文件只会报Script error,没有详细信息,怎么解决:

客户端:script标签添加crossOrigin

服务端:设置:Access-Control-Allow-Origin

iframe异常

使用window.onerror


promise异常捕获

没有写 catchPromise 中抛出的错误无法被 onerrortry-catch 捕获到

为了防止有漏掉的 Promise 异常,建议在全局增加一个对 unhandledrejection 的监听,用来全局监听Uncaught Promise Error

window.addEventListener("unhandledrejection", function(e){
  console.log(e);
});

补充:如果去掉控制台的异常显示,需要加上:event.preventDefault();

vue异常捕获

VUE errorHandler

Vue.config.errorHandler = (err, vm, info) => {
  console.error("通过vue errorHandler捕获的错误");
  console.error(err);
  console.error(vm);
  console.error(info);
}

React异常捕获

componentDidCatch(React16)

新概念Error boundary(React16):UI的某部分引起的 JS 错误不会破坏整个程序(只有 class component可以成为一个 error boundaries)

不会捕获以下错误

1.事件处理器
2.异步代码
3.服务端的渲染代码
4.在 error boundaries 区域内的错误

Eg: 全局一个error boundary 组件就够用啦

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
 
  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }
 
  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return 

Something went wrong.

; } return this.props.children; } }

页面崩溃和卡顿处理

卡顿

网页暂时响应比较慢, JS 可能无法及时执行

解决:window 对象的 loadbeforeunload 事件实现了网页崩溃的监控

window.addEventListener("load", function () {
    sessionStorage.setItem("good_exit", "pending");
    setInterval(function () {
        sessionStorage.setItem("time_before_crash", new Date().toString());
    }, 1000);
  });
  window.addEventListener("beforeunload", function () {
    sessionStorage.setItem("good_exit", "true");
  });
  if(sessionStorage.getItem("good_exit") &&
    sessionStorage.getItem("good_exit") !== "true") {
    /*
        insert crash logging code here
    */
    alert("Hey, welcome back from your crash, looks like you crashed on: " + sessionStorage.getItem("time_before_crash"));
  }

崩溃

JS 都不运行了,还有什么办法可以监控网页的崩溃,并将网页崩溃上报呢

解决:Service Worker 来实现网页崩溃的监控

Service Worker 有自己独立的工作线程,与网页区分开,网页崩溃了,Service Worker一般情况下不会崩溃;

Service Worker 生命周期一般要比网页还要长,可以用来监控网页的状态;

网页可以通过 navigator.serviceWorker.controller.postMessage API 向掌管自己的 SW发送消息。

错误上报机制

Ajax 发送数据
因为 Ajax 请求本身也有可能会发生异常,而且有可能会引发跨域问题,一般情况下更推荐使用动态创建 img 标签的形式进行上报。

动态创建 img 标签的形式 更常用,简单,无跨越问题

function report(error) {
  let reportUrl = "http://jartto.wang/report";
  new Image().src = `${reportUrl}?logs=${error}`;
}

如果你的网站访问量很大,那么一个必然的错误发送的信息就有很多条,这时候,我们需要设置采集率,从而减缓服务器的压力:

Reporter.send = function(data) {
  // 只采集 30%
  if(Math.random() < 0.3) {
    send(data)      // 上报错误信息
  }
}
js源代码压缩如何定位:成熟方案提供sentry

sentry 是一个实时的错误日志追踪和聚合平台,包含了上面 sourcemap 方案,并支持更多功能,如:错误调用栈,log 信息,issue管理,多项目,多用户,提供多种语言客户端等,

这里不过多叙述,之后在搭建sentry服务时,会再补篇博文

补充:node服务端错误处理机制

全栈开发,后端采用express库,在这里补充一下,node服务的错误处理方案

错误分类

一般错误处理:如某种回退,基本上只是说:“有错误,请再试一次或联系我们”。这并不是特别聪明,但至少通知用户,有地方错了——而不是无限加载或进行类似地处理

特殊错误处理为用户提供详细信息,让用户了解有什么问题以及如何解决它,例如,有信息丢失,数据库中的条目已经存在等等

步骤

1. 构建一个自定义 Error 构造函数:让我们方便地获得堆栈跟踪

class CustomError extends Error {
    constructor(code = "GENERIC", status = 500, ...params) {
        super(...params)
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, CustomError)
        }
        this.code = code
        this.status = status
    }
}

module.exports = CustomError

2.处理路由:对于每一个路由,我们要有相同的错误处理行为

wT:在默认情况下,由于路由都是封装的,所以 Express 并不真正支持那种方式

解决:实现一个路由处理程序,并把实际的路由逻辑定义为普通的函数。这样,如果路由功能(或任何内部函数)抛出一个错误,它将返回到路由处理程序,然后可以传给前端

const express = require("express")
const router = express.Router()
const CustomError = require("../CustomError")

router.use(async (req, res) => {
    try {
        const route = require(`.${req.path}`)[req.method]

        try {
            const result = route(req) // We pass the request to the route function
            res.send(result) // We just send to the client what we get returned from the route function
        } catch (err) {
            /*
            This will be entered, if an error occurs inside the route function.
            */
            if (err instanceof CustomError) {
                /* 
                In case the error has already been handled, we just transform the error 
                to our return object.
                */

                return res.status(err.status).send({
                    error: err.code,
                    description: err.message,
                })
            } else {
                console.error(err) // For debugging reasons

                // It would be an unhandled error, here we can just return our generic error object.
                return res.status(500).send({
                    error: "GENERIC",
                    description: "Something went wrong. Please try again or contact support.",
                })
            }
        }
    } catch (err) {
        /* 
        This will be entered, if the require fails, meaning there is either 
        no file with the name of the request path or no exported function 
        with the given request method.
        */
        res.status(404).send({
            error: "NOT_FOUND",
            description: "The resource you tried to access does not exist.",
        })
    }
})

module.exports = router

// 实际路由文件
const CustomError = require("../CustomError")

const GET = req => {
    // example for success
    return { name: "Rio de Janeiro" }
}

const POST = req => {
    // example for unhandled error
    throw new Error("Some unexpected error, may also be thrown by a library or the runtime.")
}

const DELETE = req => {
    // example for handled error
    throw new CustomError("CITY_NOT_FOUND", 404, "The city you are trying to delete could not be found.")
}

const PATCH = req => {
    // example for catching errors and using a CustomError
    try {
        // something bad happens here
        throw new Error("Some internal error")
    } catch (err) {
        console.error(err) // decide what you want to do here

        throw new CustomError(
            "CITY_NOT_EDITABLE",
            400,
            "The city you are trying to edit is not editable."
        )
    }
}

module.exports = {
    GET,
    POST,
    DELETE,
    PATCH,
}

3.构建全局错误处理机制

process.on("uncaughtException", (error: any) => {
    logger.error("uncaughtException", error)
})

process.on("unhandledRejection", (error: any) => {
    logger.error("unhandledRejection", error)
})

总结:

1.可疑区域增加 Try-Catch
2.全局监控 JS 异常 window.onerror
3.全局监控静态资源异常 window.addEventListener
4.捕获没有 CatchPromise 异常:unhandledrejection
5.VUE errorHandlerReact componentDidCatch
6.监控网页崩溃:window 对象的 loadbeforeunload
7.跨域 crossOrigin 解决

引用

http://jartto.wang/2018/11/20...

https://levelup.gitconnected....

https://zhuanlan.zhihu.com/p/... 更详细 待补充

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109644.html

相关文章

  • 关于Vue2一些值得推荐的文章 -- 五、六月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    sutaking 评论0 收藏0
  • 关于Vue2一些值得推荐的文章 -- 五、六月份

    摘要:五六月份推荐集合查看最新的请点击集前端最近很火的框架资源定时更新,欢迎一下。苏幕遮燎沈香宋周邦彦燎沈香,消溽暑。鸟雀呼晴,侵晓窥檐语。叶上初阳乾宿雨,水面清圆,一一风荷举。家住吴门,久作长安旅。五月渔郎相忆否。小楫轻舟,梦入芙蓉浦。 五、六月份推荐集合 查看github最新的Vue weekly;请::点击::集web前端最近很火的vue2框架资源;定时更新,欢迎 Star 一下。 苏...

    khs1994 评论0 收藏0
  • ajax跨域,这应该是最全的解决方案了

    摘要:关于,强烈推荐阅读跨域资源共享详解阮一峰另外,这里也整理了一个实现原理图简化版如何判断是否是简单请求浏览器将请求分成两类简单请求和非简单请求。 前言 从刚接触前端开发起,跨域这个词就一直以很高的频率在身边重复出现,一直到现在,已经调试过N个跨域相关的问题了,16年时也整理过一篇相关文章,但是感觉还是差了点什么,于是现在重新梳理了一下。 个人见识有限,如有差错,请多多见谅,欢迎提出iss...

    ytwman 评论0 收藏0
  • 前端相关大杂烩

    摘要:希望帮助更多的前端爱好者学习。前端开发者指南作者科迪林黎,由前端大师倾情赞助。翻译最佳实践译者张捷沪江前端开发工程师当你问起有关与时,老司机们首先就会告诉你其实是个没有网络请求功能的库。 前端基础面试题(JS部分) 前端基础面试题(JS部分) 学习 React.js 比你想象的要简单 原文地址:Learning React.js is easier than you think 原文作...

    fuyi501 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<