资讯专栏INFORMATION COLUMN

浅谈前端中的错误处理

ShowerSun / 2168人阅读

摘要:如何避免内存泄露内存泄漏很常见,特别是前端去写后端程序,闭包运用不当,循环引用等都会导致内存泄漏。有的时候很难避免一些可能产生内存泄漏的问题,可以利用每次调用都在一个沙箱环境下调用,用完回收调。

某一天用户反馈打开的页面白屏幕,怎么定位到产生错误的原因呢?日常某次发布怎么确定发布会没有引入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每次调用都在一个沙箱环境下调用,用完回收调。

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

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

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

相关文章

  • 浅谈前端中的错误捕获

    摘要:浅谈前端中的错误捕获某一天用户反馈打开的页面白屏幕,怎么定位到产生错误的原因呢日常某次发布怎么确定发布会没有引入呢此时捕获到代码运行的并上报是多么的重要。 浅谈前端中的错误捕获 某一天用户反馈打开的页面白屏幕,怎么定位到产生错误的原因呢?日常某次发布怎么确定发布会没有引入bug呢?此时捕获到代码运行的bug并上报是多么的重要。 既然捕获错误并上报是日常开发中不可缺少的一环,那怎么捕获到...

    li21 评论0 收藏0
  • 浅谈前端中的错误处理

    摘要:如何避免内存泄露内存泄漏很常见,特别是前端去写后端程序,闭包运用不当,循环引用等都会导致内存泄漏。有的时候很难避免一些可能产生内存泄漏的问题,可以利用每次调用都在一个沙箱环境下调用,用完回收调。 某一天用户反馈打开的页面白屏幕,怎么定位到产生错误的原因呢?日常某次发布怎么确定发布会没有引入bug呢?此时捕获到代码运行的bug并上报是多么的重要。 既然捕获错误并上报是日常开发中不可缺少的...

    BothEyes1993 评论0 收藏0
  • 浅谈Nodejs应用的主文件index.js的组成部分

    摘要:搭建一个应用,少不了一个主文件,不少人根据各自喜好来定义名字,像。总结一个完整的由个部分组成,大家只要把主文件当成白雪公主,把个组成部分当作七个小矮人就行了,哈哈,这个记法真天才。 前言 Node妹子的问世,着实让我们前端攻城狮兴奋了一把,尤其本屌听说Javascript可以写服务端后,兴奋的像是看到了二次元萝莉的胖子...(●◡●)。呃哼...YY先到这里,原谅本屌是个二次元萝莉控。...

    Profeel 评论0 收藏0
  • 浅谈Nodejs应用的主文件index.js的组成部分

    摘要:搭建一个应用,少不了一个主文件,不少人根据各自喜好来定义名字,像。总结一个完整的由个部分组成,大家只要把主文件当成白雪公主,把个组成部分当作七个小矮人就行了,哈哈,这个记法真天才。 前言 Node妹子的问世,着实让我们前端攻城狮兴奋了一把,尤其本屌听说Javascript可以写服务端后,兴奋的像是看到了二次元萝莉的胖子...(●◡●)。呃哼...YY先到这里,原谅本屌是个二次元萝莉控。...

    xioqua 评论0 收藏0
  • 浅谈ES6原生Promise

    摘要:如果有错误,则到的第二个回调函数中,对错误进行处理。假设第一个的第一个回调没有返回一个对象,那么第二个的调用者还是原来的对象,只不过其的值变成了第一个中第一个回调函数的返回值。 ES6标准出炉之前,一个幽灵,回调的幽灵,游荡在JavaScript世界。 正所谓: 世界本没有回调,写的人多了,也就有了})})})})})。 Promise的兴起,是因为异步方法调用中,往往会出现回调函数一...

    yedf 评论0 收藏0

发表评论

0条评论

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