用户反馈打开的页面白屏幕,怎么定位到产生错误的缘由呢?平常某次发布怎么肯定发布会没有引入bug呢?此时捕获到代码运行的bug并上报是多么的重要。javascript
既然捕获错误并上报是平常开发中不可缺乏的一环,那怎么捕获到错误呢?万能的try...catchhtml
try{
throw new Error()
} catch(e) {
// handle error
}
复制代码
看上去错误捕获是多么的简单,然而下面的场景下就不能捕获到了前端
try {
setTimeout(() => {
throw new Error('error')
})
} catch (e) {
// handle error
}
复制代码
你会发现上面的例子中的错误不能正常捕获,看来错误捕获并非这样简单**try...catch**就能搞定,固然你也能够为异步函数包裹一层**try...catch**来处理。java
浏览器中,**window.onerror**来捕获你的错误node
window.onerror = function (msg, url, row, col, error) {
console.log('error');
console.log({
msg, url, row, col, error
})
};
复制代码
捕获到错误后就能够将错误上报,上报方式很简单,你能够经过建立简单的**img**,经过**src**指定上报的地址,固然为了不上报发送过多的请求,能够对上报进行合并,合并上报。能够定时将数据进行上报到服务端。git
但但你去看错误上报的信息的时候,你会发现一些这样的错误**Script error**github
由于浏览器的同源策略,对于不一样域名的错误,都抛出了**Script error**,怎么解决这个问题呢?特别是如今基本上js资源都会放在cdn上面。web
解决方案数据库
1:全部的资源都放在同一个域名下。可是这样也会存在问题是不能利用cdn的优点。后端
2:增长跨域资源支持,在cdn 上增长支持主域的跨域请求支持,在script 标签加**crossorigin**属性
在使用Promise过程当中,若是你没有catch,那么能够这样来捕获错误
window.addEventListener("unhandledrejection", function(err, promise) {
// handle error here, for example log
});
复制代码
NodeJs中的错误捕获很重要,由于处理不当可能致使服务雪崩而不可用。固然了不只仅知道如何捕获错误,更应该知道如何避免某些错误。
当你写一个函数的时候,你也许曾经思考过当函数执行的时候出现错误的时候,我是应该直接抛出throw,仍是使用callback或者event emitter仍是其它方式分发错误呢?
我是否应该检查参数是不是正确的类型,是否是null
若是参数不符合的时候,你怎么办呢?抛出错误仍是经过callback等方式分发错误呢?
若是保存足够的错误来复原错误现场呢?
若是去捕获一些异常错误呢?try...catch仍是domain
操做错误每每发生在运行时,并不是因为代码bug致使,多是因为你的系统内存用完了或者是因为文件句柄用完了,也多是没有网络了等等
编码错误那就比较容易理解了,多是undefined却看成函数调用,或者返回了不正确的数据类型,或者内存泄露等等
你能够记录一下错误,而后什么都不作
你也能够重试,好比由于连接数据库失败了,可是重试须要限制次数
你也能够将错误告诉前端,稍后再试
也许你也能够直接处理,好比某个路径不存在,则建立该路径
错误编码是很差处理的,由于是因为编码错误致使的。好的办法其实重启该进程,由于
你不肯定某个编码错误致使的错误会不会影响其它请求,好比创建数据库连接错误因为编码错误致使不能成功,那么其它错误将致使其它的请求也不可用
或许在错误抛出以前进行IO操做,致使IO句柄没法关闭,这将长期占有内存,可能致使最后内存耗尽整个服务不可用。
上面提到的两点其实都没有解决问题根本,应该在上线前作好测试,并在上线后作好监控,一旦发生相似的错误,就应该监控报警,关注并解决问题
在同步函数中,直接throw出错误
对于一些异步函数,能够将错误经过callback抛出
async/await能够直接使用try..catch捕获错误
EventEmitter抛出error事件
一个NodeJs运用,仅仅从码层面是很难保证稳定运行的,还要从运维层面去保障。
单进程的nodejs一旦挂了,整个服务也就不可用了,因此我萌须要多个进程来保障服务的可用,某个进程只负责处理其它进程的启动,关闭,重启。保障某个进程挂掉后可以当即重启。
能够参考TSW中多进程的设计。master负责对worker的管理,worker和master保持这心跳监测,一旦失去,就当即重启之。
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每次调用都在一个沙箱环境下调用,用完回收调。