EventEmitter事件处理程序的延迟附件的模式?

时间:2015-01-07 00:17:21

标签: javascript node.js eventemitter

在生成事件后附加事件处理程序有一个看似众所周知的问题。这是一个问题,主要是在调用遵循返回EventEmitter模式的函数时,例如:

var EventEmitter = require('events').EventEmitter;

function    doSomethingAsync() {
    var ev = new EventEmitter(),
        something = null;

    // Caller will never see this event because its 
    // handler is bound after fact.

    if(!something) {
        ev.emit('error', 'Something is null!');
        return ev;
    }

    return ev;
}

var res = doSomethingAsync();

res.on('error', function(s) {
    console.log('Error returned: ' + s);
});

这将返回未处理的错误异常,因为在发出error时,尚未附加处理程序:

sasha@peacock:~$ node test.js
events.js:87
      throw Error('Uncaught, unspecified "error" event.');
            ^
Error: Uncaught, unspecified "error" event.
    at Error (native)
    at EventEmitter.emit (events.js:87:13)
    at doSomethingAsync (/home/sasha/test.js:11:6)
    at Object.<anonymous> (/home/sasha/test.js:18:11)
    at Module._compile (module.js:460:26)
    at Object.Module._extensions..js (module.js:478:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:501:10)
    at startup (node.js:124:16)

我能够提出的唯一解决方案是在主叫方创建EventEmitter,提前绑定处理程序,并将其传递给函数:

var EventEmitter = require('events').EventEmitter;

function	doSomethingAsync(ev) {
	var something = null;

	// Caller will never see this event because its 
	// handler is bound after fact.

	if(!something) {
		ev.emit('error', 'Something is null!');
	}
};

var res = new EventEmitter();

res.on('error', function(s) {
	console.log('Error returned: ' + s);
});

doSomethingAsync(res);

但是,这似乎不够优雅和混乱。通过第一种方法应用的异步操作的事件处理程序的唯一原因是因为所讨论的操作通常需要比函数返回的时间更长的时间来完成。这使调用者有时间将事件处理程序应用于返回的EventEmitter

当然,有一种首选的模式,习惯用法或隐藏的JavaScript或Node功能可以更好地处理这种情况吗?

我认为一种方法是不使用EventEmitters来传输验证错误或其他可能立即发生的错误,而只是返回其他内容。但我仍然认为这不是解决本质问题的方法;在Node文档和其他地方广泛描述的这个模型依赖于这样的假设:异步操作需要更长的时间来完成,而不是在返回EventEmitter之后绑定事件处理程序所花费的时间。大多数情况下,这可能是真的,但不能保证是真的。

这就是为什么我认为必须有更好的方法。如果没有,那么将使EventEmitters最佳实际使用的Node文档非常具有误导性。当然有更好的方法吗?

2 个答案:

答案 0 :(得分:1)

您可以使用process.nextTick在执行当前调用堆栈后推迟事件发送。以你的例子:

var EventEmitter = require('events').EventEmitter;

function doSomethingAsync() {
    var ev = new EventEmitter(),
        something = null;

    // Caller will see this event because it
    // will be emitted after current call stack

    if(!something) {
        process.nextTick(function() {
            ev.emit('error', 'Something is null!');
        });
        return ev;
    }

    return ev;
}

var res = doSomethingAsync();

res.on('error', function(s) {
    console.log('Error returned: ' + s);
});

答案 1 :(得分:0)

(希望能对某人有所帮助的很晚的答案)

不要依赖process.nextTick(),它可能不能保证会发出后续事件(并且可能会在以后发生一些混乱的事情),最好的技巧是扩展您的发射器并将延迟的事件推入堆栈并通过FIFO解决。将回调传递给扩展的发射器并接收响应 这是工作代码,

var EventEmitter = require('events').EventEmitter;
const util = require('util');
const events = require('events');

const DeferredEventEmitter = function(next) {
   EventEmitter.call(this);
   this.defferedEvents = [];  // stack or cache those require deferred execution

   //resolve the events
   this.on('error', function(s) { 
    //console.log(this.defferedEvents)
    //console.log('Error returned: ' + s);
    if(next) 
        next(s)  //execute the callback if error
   })

   this.on('success', function(s) { 
    //console.log('Success!' + s)
    if(next)
        next(s)
   })
   
   //here add further events to be resolved
   //this.on('some_event', function().....

}
 //inherit and apply native event emitter to our 
util.inherits(DeferredEventEmitter , events.EventEmitter);  

// push to stack
DeferredEventEmitter.prototype.deferredEmit = function(name, payload) {
  this.defferedEvents.push({name, payload});
}

// resolve each deferred event (FIFO)
DeferredEventEmitter.prototype.broadcastDeferredEmit = function() {
if(!this.defferedEvents.length)
    return;
  while(this.defferedEvents.length) { 
    const event = this.defferedEvents.shift();
    this.emit(event.name, event.payload);
  }
}

// The caller
function   doSomethingAsync(callback) {
    var ev = new DeferredEventEmitter(callback),
        something = null, everything = "not null";
    // Caller will never see this event because its 
    // handler is bound after fact.
    if(!something) 
       //push to stack the deferredEmit to later resolve
       ev.deferredEmit('error', 'emitting because something is "null"!');  
    
    if(everything) 
        ev.deferredEmit('success', 'emitting because, everything is "not null"!')

    console.log(ev.defferedEvents)
    //now broadcast all deferred emits
    ev.broadcastDeferredEmit()
    return ev
}
//call function and receive deferred emits without calling for it.
var res = doSomethingAsync((res) => { 
    console.log(res)
});


//module.exports = DeferredEventEmitter ;

我希望这是您想要的。