3

我正在使用全局事件发射器编写 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');
        });
    }

});

这样做的原因是因为发射现在延迟到当前事件循环完成,因此所有侦听器都已注册。

但是,这种方法存在很多问题:

  1. 这种基于事件的架构的优点之一是发射模块不需要知道谁在监听他们的事件。因此,没有必要决定是否需要延迟事件发射,因为人们不知道要监听什么事件以及它是否需要延迟。
  2. 它显着地使代码混乱和复杂化(比较两个示例)
  3. 它可能会降低性能

因此,我的问题是:如何避免将事件发射延迟到事件循环的下一个滴答声,例如在所描述的情况下?

更新 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都会发出原始请求,那么上述情况是不可能的,因为每个事件都与另一个请求相关,并且您无法侦听针对特定请求并行执行的操作组合。

4

4 回答 4

3

同时拥有一个mediatorwhich 监听事件和一个emitterwhich 也监听和触发事件似乎过于复杂。我确信有正当理由,但我的建议是简化。我们在我们的 nodejs 服务中使用了一个 globaleventBus来做类似的事情。对于这种情况,我会发出一个新事件。

bus.on('http:request', function(req, res) {
  if (req.url === '/')
    bus.emit('ns:root', req, res);
  else
    bus.emit('404');
});

// note the use of namespace here to target specific subsystem
bus.once('ns:root', function(req, res) {
  res.send('Welcome to the frontpage.');
});
于 2012-11-17T04:21:09.373 回答
1

听起来您开始遇到观察者模式的一些缺点(如许多描述此模式的书籍/文章中所述)。我的解决方案并不理想——假设存在一个理想的解决方案——但是:

如果您可以做一个简化的假设,即每个发射器仅发出 1 次事件(即emitter.emit('root');,对于任何实例仅调用一次emitter),那么也许您可以编写类似于 jQuery$.ready()事件的东西。

在这种情况下,订阅emitter.once('root', function() { ... })将检查是否'root'已经发出,如果是,则无论如何都会调用处理程序。如果'root'尚未发出,它将遵循正常的现有功能。

这就是我得到的。

于 2012-11-15T20:00:47.747 回答
1

我认为这种架构有问题,因为您正在执行需要确定的操作顺序的顺序工作 (I/O),但仍计划在自然允许不确定的执行顺序的组件上构建应用程序。

你可以做什么

在 mediator.on 函数中包含上下文选择器,例如以这种方式

mediator.on('http.request > root', function( .. ) { } )

或将其定义为子中介

var submediator = mediator.yield('http.request > root');
submediator.on(function( ... ) {
     emitter.once('root', ... )
}); 

仅当从 http.request 处理程序发出 root 时,这才会触发回调。

另一种更棘手的方法是进行后台排序,但是对于您当前的一个调解器将它们全部管理接口是不可行的。实现代码,这样每个 .emit 调用实际上并不发送事件,而是将产生的事件放入列表中。每个 .once 将消费事件记录放在同一个列表中。当所有 mediator.on 回调都已执行时,遍历列表,按依赖顺序对其进行排序(例如,如果列表首先消耗 'root' 然后产生 'root' 交换它们)。然后按顺序执行消费处理程序。如果您用完了事件,请停止执行。

于 2012-11-16T14:33:52.990 回答
0

哦,这似乎是一个非常破碎的架构,原因如下:

  1. 你如何传递requestresponse?看起来您已经获得了对它们的全局引用。
  2. 如果我回答你的问题,你会将你的服务器变成一个纯粹的同步函数,你会失去异步 node.js 的力量。(请求将有效地排队,并且只有在最后一个请求 100% 完成后才能开始执行。)

要解决这个问题:

  1. request将&作为参数传递response给调用。emit()现在您不再需要强制所有内容同步运行,因为当下一个组件处理事件时,它将引用正确的请求和响应对象。
  2. 了解其他不需要全局调解器的常见解决方案。看看多年前 Connect 基于 Internet 的模式:http: //howtonode.org/connect-it <- 描述中间件/洋葱路由
于 2012-11-07T15:14:08.930 回答