【问题标题】:Pattern for deferred attachment of EventEmitter event handlers?EventEmitter 事件处理程序的延迟附加模式?
【发布时间】:2020-09-06 20:43:44
【问题描述】:

在已经生成事件之后附加事件处理程序有一个看似众所周知的问题。这主要是在调用遵循返回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 文档非常具有误导性。肯定有更好的方法吗?

【问题讨论】:

  • 我认为理想的做法是让 EventEmitter 队列发射事件,直到它们的监听器被绑定,然后才发射它们。

标签: javascript node.js eventemitter


【解决方案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);
});

【讨论】:

  • 这正是我想要的,谢谢!我唯一的问题是: process.nextTick() 文档说:“在事件循环周围的下一个循环中调用这个回调。”虽然它似乎有效,但事件循环滴答声与所需调用堆栈的全部范围之间的显式联系是什么?换句话说,虽然初始化函数可能需要在事件循环迭代之前完成,但如何保证调用者中的后续 .on() 事件侦听器附件将在同一滴答声中完成?我假设什么...
  • @alex-balashov 在节点 v0.11.x(将是 0.12)process.nextTick 通过 v8 微任务实现(与 PromiseObject.observe 相同)。微任务在当前调用堆栈的当前执行之后立即执行(换句话说 - 在同一个滴答声中)。因此,如果您使用process.nextTick,请不要担心循环迭代。其实nextTick是个很不幸的名字。
  • @AlexBalashov 如果您想在 NEXT TICK 上执行代码,请使用 setImmediate。如果您只想将代码的执行添加到队列中,请使用setTimeout。看这里youtube.com/…。这是关于事件循环的非常简单的解释。
  • 任何依赖 setTimeout() 的方法都会假设返回 EventEmitter 的函数需要多长时间才能返回,以及 .on() 侦听器需要多长时间。虽然这些假设可能是正确的,并且一切都会正常工作,但我想要一种更正式的方法。
  • setTimeout 只是将您的回调添加到任务队列(另一个名称 - 消息队列、事件队列)。它不能中断你当前的执行(除了抛出错误之外什么都不能)。当您调用setTimeout(callback, 1000) 时,它只是保证该回调将在 1 秒后添加到任务队列中,但可以在 1 分钟后执行(如果您的任务队列人员充足)。因此,EventEmitter 需要多长时间才能返回,以及 .on() 侦听器需要多长时间都没有关系。只要EventEmitter.on() 在同一个调用栈中,setTimeout(c, 0) 就会一直有效。
【解决方案2】:

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

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

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 ;

我希望这就是你要找的。​​p>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-05
    相关资源
    最近更新 更多