异步异常
处理 异步异常特征
由于节点的回调异步,是不可能捕获所有异常的catch:
{试
process.nexttick(
函数(){()
foo.bar();
});
} catch(
错误){
无法捕捉到它
}
就Web
服务而言,这是非常希望的:
快速
类型路由 app.get( /索引的
功能(REQ,RES){
{试
业务逻辑
} catch(错误){
Logger.error(ERR);
res.statuscode = 500;
返回res.json({成功:假
消息:
服务器异常});
}
});
如果尝试捕获可以捕获所有异常,当代码有一些意外错误时,我们可以记录一些错误,同时返回一个500错误给调用者友好。不幸的是,尝试捕获不能捕获异步中的异常。所以我们唯一能做的就是:
app.get( /索引的功能(REQ,RES){
业务逻辑
});
Process.on ('uncaughtException', function (ERR) {
Logger.error(ERR);
});
这时,虽然我们可以记录错误的日志,
进程不会
退出,但是我们没有办法返回错误的请求,只能使它及时返回。
域
在节点V0.8 +版本的时候,一个模块领域出版了。这是什么模块不try catch不能做:捕捉异步回调的例外。
因此,上面这个无助的例子似乎有了一个
解决方案:
VaR域=需要('domain);
引入域中间件,每个请求将被包装在一个单独的域中。
用于处理异常的域
app.use(功能(REQ,RES,下){
var a domain.create();
域监视错误事件
D.on(错误,函数(ERR){
Logger.error(ERR);
res.statuscode = 500;
res.json({成功:虚假,消息:服务器例外});
D.dispose();
});
D.增加(需求);
D.加(RES);
D.run(下);
});
app.get( /索引的功能(REQ,RES){
企业
});
我们以中间件的形式引入域来处理异步中的异常。当然,虽然域捕获异常,但由于异常堆栈丢失会导致
内存泄漏,所以当发生这种
情况或需要
重启进程时,有兴趣的同学可以查看域中间件域中间件。
一个奇怪的
故障 我们所有的测试
都是正常的,当它们在生产环境中被正式使用时,我们
发现域名突然失败了!它没有在异步捕获异常,最终导致异常退出的过程。经过调查,最终发现是通过Redis来存储会话的引入使。
var http =需要('http');
VaR
连接=需要('connect);
无功redisstore =需要('connect-redis ')(连接);
无功domainmiddleware =需要('domain-middleware);
VaR服务器= http.createserver();
var =连接();
app.use(connect.session({)
关键:关键的,
秘密:秘密,
店:新redisstore(6379,'localhost)
});
/ / domainmiddleware使用可以看前面的链接
app.use(domainmiddleware({)
服务器:服务器,
KillTimeout:30000
});
此时,当我们的业务逻辑代码中有一个异常时,它被发现不被域捕获!经过几次尝试,终于找到了问题所在。
VaR域=需要('domain);
VaR redis =需要('redis);
VaR缓存= redis.createclient(6379,'localhost);
函数错误(){
cache.get('a',函数(){(){
把新的错误(错误);
});
}
函数ok(){
setTimeout(){()函数(
把新的错误(错误);
},100);
}
var a domain.create();
D.on(错误,函数(ERR){
console.log(ERR);
});
D.run(OK); / /域捕获到异常
D.run(错误); / /抛出异常
奇怪的!所有都是异步调用,为什么前者被捕获,后者不能捕获它
域分析
回首过去,让我们看看域所做的使我们能够捕获异步请求(从节点v0.10.4,可迅速改变和
优化代码)。
节点事件循环机制
以前我们看域原则,让我们知道nexttick和_tickcallback两方面。
功能latercall(){
console.log('print我以后);
}
process.nexttick(latercallback);
console.log('print我第一);
上面的代码是人写的节点的熟悉,和nexttick的
作用是把latercallback在接下来的事件循环
执行,_tickcallback
方法是一种非公开方式。它被称为入口函数,在循环结束后继续下一个事件循环。
换句话说,节点
维护的事件循环nexttick团队队列,_tickcallback柱。
实现域
在了解了节点的事件循环机制之后,让我们看看域已经做了什么。
域本身实际上是通过捕获错误EventEmitter对象通过一个事件。所以当我们研究它,我们可以简化为两点:
何时触发域的错误事件:
进程抛出异常,不会被任何catch捕获。这将触发此时整个过程的processfatal。如果域包裹,它会触发错误事件域,否则,它会触发事件过程的uncaughtException。
域是如何在许多不同的事件周期中传递的:
当域名被实例化时,我们通常叫它的run方法,如在Web服务领域的例子,在包的执行功能。当包裹函数被执行时,该process.domain全局变量将指向域实例。当这一事件循环抛出,当异常被抛出processfatal研究发现,process.domain的存在,和错误事件是在域触发。
后域模块
介绍中的要求,全球nexttick和_tickcallback改写,有些领域相关的代码注入。
/简化域
传输代码
功能nextdomaintick(回调){
NextTickQueue.push({回调:回调,域的过程。域});
}
功能_tickdomaincallback(){
VaR滴答= nexttickqueue.pop();
/ / process.domain = tock.domain
Tock.domain tock.domain.enter();
回调();
/ /清晰的process.domain
Tock.domain tock.domain.exit();
}
};
这是一批在流通领域转移的关键事件:nexttick团队,记录当前域,当添加到队列中的周期事件是_tickcallback开始实施的时候,在process.domain记录域事件的新周期。这样,在代码包由域,不管怎么叫process.nexttick,域将通过。
当然,有一个异步节点两种情况,在事件的形式。所以在构造函数具有以下代码EventEmitter:
如果(出口。usingdomains){
如果有一个活动的域,然后附加到它。
域为域需要('domain)| |;
如果(domain.active!(这是域。域)){
this.domain = domain.active;
}
}
当实例化EventEmitter,它将绑定对象到当前域。当发出触发此对象上的一个事件,就像_tickcallback执行时,回调函数将重新由当前域包裹。
另一个案例中,setTimeout和setInterval,同样,在计时器的源代码,我们也可以找到这样的代码:
如果(过程域)timer.domain = process.domain;
像EventEmmiter,定时器的回调函数也会被包裹在当前域。
节点插入域代码中的三个关键的地方nexttick,定时器,和事件允许他们通过不同的事件循环。
更复杂的领域
在某些情况下,我们可能会遇到更复杂域使用的需求。
域嵌套:我们可以在外层中拥有域,内层还有其他域,并且可以在
文档中找到使用场景。
为服务器
创建一个顶级域
无功serverdomain = domain.create();
ServerDomain.run(function(){()
服务器是在serverdomain范围/创建
http.createserver(功能(REQ,RES){
要求和RES也创作了/在serverdomain范围
但是,我们希望每个请求都有一个单独的域。
首先 / /创造的东西和添加要求和研究它。
VAR必须= domain.create();
Reqd.add(需求);
Reqd.add(RES);
Reqd.on(错误,函数(ER){
Console.error(错误的,呃,请求的URL);
{试
(500)res.writehead;
res.end(错误发生了,对不起。);
} catch(ER){
Console.error(错误发送500,呃,请求的URL);
}
});
}听(1337);
});
为了实现这个功能,事实上,域也会秘密地维护一个堆栈域,感兴趣的童鞋可以在这里看到。
回去解决疑问
回过头来看,我们将看到刚才的问题:为什么这两个看起来相同的异步调用,但一个域不能捕捉异常在了解原理,不难想到,调用redis是在领域范围的异步调用,这是被错误的事件循环。让我们看一个短的代码来看看是哪里的问题。
VaR域=需要('domain);
Var EventEmitter要求EventEmitter('events);
var =新EventEmitter();
VaR定时器= setTimeout()函数(){
E.emit(数据的);
},10);
函数下一步(){
E.once(数据,函数(){(){
把新的错误(错);
});
}
var a domain.create();
D.on(错误,函数(){(){
console.log('cache由域);
});
D.run(下);
我们还发现,误差不会域捕获的
原因是显而易见的:在初始化时间定时器和两个关键对象不在域的范围因此,当
显示器在事件的下一个函数被触发,回调函数执行异常抛出,实际上是域包,当然不会被捕获的异常域!
事实上,节点专门设计一个API:这种情况domain.add。它可以定时和事件对象的当前域添加外部域。对于上面的例子:
添加(定时器);
或
d.加(e);
将任何定时器或E对象添加到域中可以让该错误被域捕获。
看那第一个redis原因域无法捕获异常的问题。我们有一个解决的办法,太
事实上,在这种情况下,仍然没有办法达到最佳的解决方案。现在,当发生意外异常时,我们只能让当前请求超时,然后让进程停止服务,然后重新
启动。
__domain是非常强大的,但不是万能的。__希望看完这篇文章后,您可以使用
空间,避免踩到坑。
以上是本文的全部内容,希望能对您有所帮助,希望大家多多
支持。