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

Pattern for deferred attachment of EventEmitter event handlers?

在已生成事件后附加事件处理程序时,存在一个看似众所周知的问题。这主要是在调用遵循 returning 和 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);
});

这将 return 一个未处理的错误异常,因为在发出 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);

不过,这看起来既不优雅又凌乱。通过第一种方法应用的异步操作的事件处理程序完全起作用的唯一原因是因为所讨论的操作通常需要比函数完成 return 更长的时间才能完成。这使调用者有时间将事件处理程序应用于 returned EventEmitter.

肯定有 JavaScript 或 Node 功能的首选模式、习语或隐藏位可以更好地处理这种情况?

我想一种方法是不使用 EventEmitters 来传输验证错误或其他可能瞬间发生的错误,而只是 return 其他东西。但我仍然认为这不是解决根本问题的办法;这个模型在 Node 文档和其他地方被广泛描述,它依赖于异步操作完成的时间比在 EventEmitter 被 returned 后绑定事件处理程序所花费的时间更长的假设。大多数时候,这可能是真的,但不能保证它是真的。

这就是为什么我认为必须有更好的方法。如果没有,那会使关于 EventEmitters 最佳实践使用的 Node 文档非常具有误导性。肯定有更好的方法吗?

您可以使用 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);
});

(一个很晚的回答希望它能帮助某人)

不要依赖 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 ;

希望这就是您要找的。