浅谈前端错误处理

栏目: Node.js · 发布时间: 5年前

内容简介:用户反馈打开的页面白屏幕,怎么定位到产生错误的原因呢?日常某次发布怎么确定发布会没有引入bug呢?此时捕获到代码运行的bug并上报是多么的重要。既然捕获错误并上报是日常开发中不可缺少的一环,那怎么捕获到错误呢?万能的看上去错误捕获是多么的简单,然而下面的场景下就不能捕获到了

用户反馈打开的页面白屏幕,怎么定位到产生错误的原因呢?日常某次发布怎么确定发布会没有引入bug呢?此时捕获到代码运行的bug并上报是多么的重要。

既然捕获错误并上报是日常开发中不可缺少的一环,那怎么捕获到错误呢?万能的 try...catch

try{

throw new Error()

} catch(e) {

// handle error

}
复制代码

看上去错误捕获是多么的简单,然而下面的场景下就不能捕获到了

try {

setTimeout(() => {

throw new Error('error')

})

} catch (e) {

// handle error

}
复制代码

你会发现上面的例子中的错误不能正常捕获,看来错误捕获并不是这样简单**try...catch**就能搞定,当然你也可以为异步函数包裹一层**try...catch**来处理。

浏览器中,**window.onerror**来捕获你的错误

window.onerror = function (msg, url, row, col, error) {

console.log('error');

console.log({

msg, url, row, col, error

})

};
复制代码

捕获到错误后就可以将错误上报,上报方式很简单,你可以通过创建简单的**img**,通过**src**指定上报的地址,当然为了避免上报发送过多的请求,可以对上报进行合并,合并上报。可以定时将数据进行上报到服务端。

但但你去看错误上报的信息的时候,你会发现一些这样的错误**Script error**

因为浏览器的同源策略,对于不同域名的错误,都抛出了**Script error**,怎么解决这个问题呢?特别是现在基本上js资源都会放在cdn上面。

解决方案

1:所有的资源都放在同一个域名下。但是这样也会存在问题是不能利用cdn的优势。

2:增加跨域资源支持,在cdn 上增加支持主域的跨域请求支持,在script 标签加**crossorigin**属性

在使用 Promise 过程中,如果你没有 catch ,那么可以这样来捕获错误

window.addEventListener("unhandledrejection", function(err, promise) { 
    // handle error here, for example log   
});
复制代码

如何在NodeJs中捕获错误

NodeJs中的错误捕获很重要,因为处理不当可能导致服务雪崩而不可用。当然了不仅仅知道如何捕获错误,更应该知道如何避免某些错误。

  • 当你写一个函数的时候,你也许曾经思考过当函数执行的时候出现错误的时候,我是应该直接抛出 throw ,还是使用 callback 或者 event emitter 还是其它方式分发错误呢?

  • 我是否应该检查参数是否是正确的类型,是不是 null

  • 如果参数不符合的时候,你怎么办呢?抛出错误还是通过 callback 等方式分发错误呢?

  • 如果保存足够的错误来复原错误现场呢?

  • 如果去捕获一些异常错误呢? try...catch 还是 domain

操作错误VS编码错误

1. 操作错误

操作错误往往发生在运行时,并非由于代码bug导致,可能是由于你的系统内存用完了或者是由于文件句柄用完了,也可能是没有网络了等等

2.编码错误

编码错误那就比较容易理解了,可能是 undefined 却当作函数调用,或者返回了不正确的数据类型,或者内存泄露等等

处理操作错误

  • 你可以记录一下错误,然后什么都不做

  • 你也可以重试,比如因为链接数据库失败了,但是重试需要限制次数

  • 你也可以将错误告诉前端,稍后再试

  • 也许你也可以直接处理,比如某个路径不存在,则创建该路径

处理编码错误

错误编码是不好处理的,因为是由于编码错误导致的。好的办法其实重启该进程,因为

  • 你不确定某个编码错误导致的错误会不会影响其它请求,比如建立数据库链接错误由于编码错误导致不能成功,那么其它错误将导致其它的请求也不可用

  • 或许在错误抛出之前进行IO操作,导致IO句柄无法关闭,这将长期占有内存,可能导致最后内存耗尽整个服务不可用。

  • 上面提到的两点其实都没有解决问题根本,应该在上线前做好测试,并在上线后做好监控,一旦发生类似的错误,就应该监控报警,关注并解决问题

如何分发错误

  • 在同步函数中,直接 throw 出错误

  • 对于一些异步函数,可以将错误通过 callback 抛出

  • async/await可以直接使用 try..catch 捕获错误

  • EventEmitter抛出 error 事件

NodeJs的运维

一个NodeJs运用,仅仅从码层面是很难保证稳定运行的,还要从运维层面去保障。

多进程来管理你的应用

单进程的nodejs一旦挂了,整个服务也就不可用了,所以我萌需要多个进程来保障服务的可用,某个进程只负责处理其它进程的启动,关闭,重启。保障某个进程挂掉后能够立即重启。

可以参考 TSW 中多进程的设计。master负责对worker的管理,worker和master保持这心跳监测,一旦失去,就立即重启之。

domain

process.on('uncaughtException', function(err) {
    console.error('Error caught in uncaughtException event:', err);
});
process.on('unhandleRejection', function(err) {
  // TODO
})
复制代码

上面捕获nodejs中异常的时候,可以说是很暴力。但是此时捕获到异常的时候,你已经失去了此时的上下文,这里的上下文可以说是某个请求。假如某个web服务发生了一些异常的时候,还是希望能够返回一些兜底的内容,提升用户使用体验。比如服务端渲染或者同构,即使失败了,也可以返回个静态的html,走降级方案,但是此时的上下文已经丢失了。没有办法了。

function domainMiddleware(options) {
    return async function (ctx, next) {
        const request = ctx.request;
        const d = process.domain || domain.create();
        d.request = request;
        let errHandler = (err) => {
            ctx.set('Content-Type', 'text/html; charset=UTF-8');
            ctx.body = options.staticHtml;
        };
        d.on('error', errHandler);
        d.add(ctx.request);
        d.add(ctx.response);
        try {
            await next();
        } catch(e) {
            errHandler(e)
        }
    }
复制代码

上面是一个简单的koa2的domain的中间件,利用domain监听 error 事件,每个请求的Request, Response对象在发生错误的时候,均会触发 error 事件,当发生错误的时候,能够在有上下文的基础上,可以走降级方案。

如何避免内存泄露

内存泄漏很常见,特别是前端去写后端程序,闭包运用不当,循环引用等都会导致内存泄漏。

  • 不要阻塞Event Loop的执行,特别是大循环或者IO同步操作

    for ( var i = 0; i < 10000000; i++ ) {
        var user       = {};
        user.name  = 'outmem';
        user.pass  = '123456';
        user.email = 'outmem[@outmem](/user/outmem).com';
    }
    复制代码

    上面的很长的循环会导致内存泄漏,因为它是一个同步执行的代码,将在进程中执行,V8在循环结束的时候,是没办法回收循环产生的内存的,这会导致内存一直增长。还有可能原因是,这个很长的执行,阻塞了node进入下一个Event loop, 导致队列中堆积了太多等待处理已经准备好的回调,进一步加剧内存的占用。那怎么解决呢?

    可以利用 setTimeout 将操作放在下一个loop中执行,减少长循环,同步IO对进程的阻.阻塞下一个loop 的执行,也会导致应用的性能下降

  • 模块的私有变量和方法都会常驻在内存中

var leakArray = [];   
exports.leak = function () {  
  leakArray.push("leak" + Math.random());  
};
复制代码

在node中require一个模块的时候,最后都是形成一个单例,也就是只要调用该函数一下,函数内存就会增长,闭包不会被回收,第二是 leak 方法是一个私有方法,这个方法也会一直存在内存。加入每个请求都会调用一下这个方法,那么内存一会就炸了。

这样的场景其实很常见

// main.js
function Main() {
  this.greeting = 'hello world';
}
module.exports = Main;
复制代码
var a = require('./main.js')();
var b = require('./main.js')();
a.greeting = 'hello a';
console.log(a.greeting); // hello a
console.log(b.greeting); // hello a
复制代码

require得到是一个单例,在一个服务端中每一个请求执行的时候,操作的都是一个单例,这样每一次执行产生的变量或者属性都会一直挂在这个对象上,无法回收,占用大量内存。

其实上面可以按照下面的调用方式来调用,每次都产生一个实例,用完回收。

var a = new require('./main.js');
// TODO
复制代码

有的时候很难避免一些可能产生内存泄漏的问题,可以利用 vm 每次调用都在一个沙箱环境下调用,用完回收调。

  • 最后就是避免循环引用了,这样也会导致无法回收

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

规划算法

规划算法

拉瓦利 / 2011-1 / 99.00元

《规划算法》内容简介:规划是人类智慧的结晶,规划问题广泛地存在于人们的日常工作和生活中。现在,规划已涉及计算机科学、人工智能、力学、机械学、控制论、对策论、概率论、图论、拓扑学、微分几何、代数几何等许多现代科学领域。《规划算法》是作者多年来教学和科研工作的总结,系统地介绍了规划领域中的基础知识和最新成果。作者将三个相对独立的学科:机器人学、人工智能和控制论巧妙地结合在一起。《规划算法》给出了大量内......一起来看看 《规划算法》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试