我正在使用全局事件发射器编写 Node.js 应用程序。换句话说,我的应用程序完全围绕事件构建。我发现这种架构对我来说非常有效,除了我将在此处描述的一个侧面案例。
请注意,我认为回答这个问题不需要 Node.js 知识。因此,我会尽量保持抽象。
想象以下情况:
- 全局事件发射器(称为
mediator
)允许各个模块侦听应用程序范围的事件。 - 创建一个 HTTP 服务器,接受传入的请求。
- 对于每个传入的请求,都会创建一个事件发射器来处理特定于该请求的事件
传入请求的示例(纯粹是为了说明这个问题):
mediator.on('http.request', request, response, emitter) {
//deal with the new request here, e.g.:
response.send("Hello World.");
});
到目前为止,一切都很好。现在可以通过识别请求的 URL 并发出适当的事件来扩展此应用程序:
mediator.on('http.request', request, response, emitter) {
//identify the requested URL
if (request.url === '/') {
emitter.emit('root');
}
else {
emitter.emit('404');
}
});
在此之后可以编写一个处理根请求的模块。
mediator.on('http.request', function(request, response, emitter) {
//when root is requested
emitter.once('root', function() {
response.send('Welcome to the frontpage.');
});
});
看起来不错,对吧?实际上,它可能是损坏的代码。原因是该行emitter.emit('root')
可能在该行之前执行emitter.once('root', ...)
。结果是监听器永远不会被执行。
root
可以通过将事件的发射延迟到事件循环的末尾来处理这种特定情况:
mediator.on('http.request', request, response, emitter) {
//identify the requested URL
if (request.url === '/') {
process.nextTick(function() {
emitter.emit('root');
});
}
else {
process.nextTick(function() {
emitter.emit('404');
});
}
});
这样做的原因是因为发射现在延迟到当前事件循环完成,因此所有侦听器都已注册。
但是,这种方法存在很多问题:
- 这种基于事件的架构的优点之一是发射模块不需要知道谁在监听他们的事件。因此,没有必要决定是否需要延迟事件发射,因为人们不知道要监听什么事件以及它是否需要延迟。
- 它显着地使代码混乱和复杂化(比较两个示例)
- 它可能会降低性能
因此,我的问题是:如何避免将事件发射延迟到事件循环的下一个滴答声,例如在所描述的情况下?
更新 19-01-2013
一个说明此行为为何有用的示例:允许并行处理 http 请求。
mediator.on('http.request', function(req, res) {
req.onceall('json.parsed', 'validated', 'methodoverridden', 'authenticated', function() {
//the request has now been validated, parsed as JSON, the kind of HTTP method has been overridden when requested to and it has been authenticated
});
});
如果每个事件json.parsed
都会发出原始请求,那么上述情况是不可能的,因为每个事件都与另一个请求相关,并且您无法侦听针对特定请求并行执行的操作组合。