简体   繁体   English

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

[英]Pattern for deferred attachment of EventEmitter event handlers?

There is a seemingly well-known problem with attaching event handlers after an event has already been generated.在已经生成事件之后附加事件处理程序有一个看似众所周知的问题。 This is a problem mainly when invoking functions that follow the pattern of returning an EventEmitter , for example:这主要是在调用遵循返回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);
});

This will return an unhandled error exception, because at the time error is emitted, no handler for it is attached yet:这将返回一个未处理的错误异常,因为在发出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)

The only solution I've been able to come up with is to create an EventEmitter on the calling side, bind the handlers in advance, and pass it to the function:我能想出的唯一解决方案是在调用端创建一个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);

This seems inelegant and messy, though.不过,这看起来既不优雅又凌乱。 The only reason event handlers on asynchronous operations that are applied via the first method work at all is because the operations in question usually take longer to complete than the function does to return.通过第一种方法应用的异步操作上的事件处理程序起作用的唯一原因是,有问题的操作通常需要比函数返回的时间更长的时间才能完成。 That gives the caller time to apply the event handlers to the returned EventEmitter .这使调用者有时间将事件处理程序应用于返回的EventEmitter

Surely there is a preferred pattern, idiom, or hidden bit of JavaScript or Node functionality to handle this case better?肯定有一种首选模式、习惯用法或隐藏的 JavaScript 或 Node 功能可以更好地处理这种情况吗?

I suppose one approach is to not use EventEmitters to transmit validation errors or other errors that can occur instantaneously, but simply return something else.我想一种方法是不使用EventEmitters来传输验证错误或其他可能立即发生的错误,而只是返回其他内容。 But I still think that's not a solution to the essential problem;但我仍然认为这不是根本问题的解决方案; this model, widely portrayed in Node documentation and elsewhere, relies on the assumption that the async operation will take longer to complete than the time it takes to bind the event handlers after the EventEmitter is returned.这个模型在 Node 文档和其他地方得到了广泛的描述,它依赖于这样一个假设,即异步操作需要比在EventEmitter返回后绑定事件处理程序所需的时间更长的时间来完成。 Most of the time, that's probably true, but it cannot be guaranteed to be true.大多数时候,这可能是真的,但不能保证是真的。

That's why I think there must be a better way.这就是为什么我认为必须有更好的方法。 If there isn't, that would make Node documentation on best-practical use of EventEmitters very misleading.如果没有,那会使 Node 文档中关于EventEmitters最佳使用方法的EventEmitters非常具有误导性。 Surely there is a better way?当然有更好的方法吗?

You can use process.nextTick to defer event emitting after current call stack being executed.您可以使用process.nextTick在当前调用堆栈执行后推迟事件发射。 For your example:对于您的示例:

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);
});

(A very late answer hoping that it would help someone ) (一个很晚的答案,希望它能帮助某人)

Do not rely on process.nextTick() and it may not guarantee the subsequent events emitted (and a little messy things may occur down the road), the best trick is to extend your emitter and push the deferred events to stack and resolve it by FIFO.不要依赖 process.nextTick() 并且它可能无法保证发出的后续事件(并且可能会发生一些混乱的事情),最好的技巧是扩展您的发射器并将延迟事件推送到堆栈并通过先进先出。 Pass a callback to the extended emitter and receive response here's the working code,将回调传递给扩展发射器并接收响应,这是工作代码,

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 ;

I hope this is what you're looking for.我希望这就是你要找的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM