-
-
Notifications
You must be signed in to change notification settings - Fork 70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make emit()
wait for all listeners to settle
#19
Conversation
This is quite an interesting tradeoff! I prefer the current behavior, since it's more analogous to |
@sindresorhus what do you think? |
The current implementation support Node's EventEmitter default error handler with It doesn't support handling errors like Node's EventEmitter's "error" event but I can't think of a use case that would require the producer to handle every errors with Waiting for all listener responses to settle could be useful for concurrent event handling, e.g. to restrict how many events can be processed concurrently. |
I think you're right that from the producer's perspective it's more important to know when all listeners completed than whether a particular listener erred. @sindresorhus what do you think? |
@dinoboff Why did you close? |
My mistake, I though it was fix. ps: I though @novemberborn commits were in an other PR. |
Supporting something like My feeling is having an |
Similarly, I've been wondering about what happens when multiple listeners throw. It still seems wrong to just lose those failures. Thus the only way to safely write listeners is to put Perhaps |
@dinoboff @sindresorhus how do you think |
That is to say, I'm not convinced listeners should be able to impact whether I think if we allocate a new |
I agree. I don't think listener should be able to make
How about both? If the user doesn't subscribe to the |
* The promise returned by emit() settles once all listeners do * If no listener threw an exception, the promise is fulfilled with `undefined` * Otherwise it's rejected with the first exception
3ba5cc4
to
dddf500
Compare
@sindresorhus Something like that? |
@sindresorhus If we do have a separate event I think we should export a dedicated instance to which those errors are routed. Normal instances shouldn't emit events when consuming code fails. We'd have to document that this should not be used by library code, only by applications. |
@dinoboff I can't immediately see why the behavior is different, but I don't think emitting I don't think listeners should (indirectly) influence what events are emitted. That implies separate infrastructure for application developers to be able to capture these errors. But that's tricky too since they may not be aware of Emittery or there may be multiple copies of |
dddf500
to
144540c
Compare
@novemberborn How can we leave them unhandled while catching them to make sure Do you mean we should just swallow them? |
The uncovered part is the one letting node log the handled rejections. I don't know how to test them since ava with fail if there are any handled rejection. |
I think we'd need to allocate a new
You could add an |
That's what the PR
Great. I will remove the |
8501e60
to
636e275
Compare
@novemberborn I got 100% coverage back but some test will keep catching handled rejection if they timeout I think. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some test will keep catching handled rejection if they timeout I think.
How do you mean?
I think we need two changes, and perhaps we should try-await-catch
instead. What do you think?
test/_run.js
Outdated
|
||
emitter.on('🦄', () => Promise.reject(first)); | ||
emitter.on('🦄', () => Promise.reject(second)); | ||
emitter.onAny(eventName => eventName !== 'error' && Promise.reject(third)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need this eventName
guard.
test/_run.js
Outdated
@@ -390,4 +426,25 @@ module.exports = Emittery => { | |||
const emitter = new Emittery(); | |||
t.throws(() => emitter.listenerCount(42), TypeError); | |||
}); | |||
|
|||
function catchUnhandledRejection(limit) { | |||
return new PCancelable((onCancel, resolve) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PCancelable
doesn't seem necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have like to be able to cancel the unhandledRejection
listener in case of failure, but it won't work with a cancellable promise. I will try something else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't observe test failures though.
...staticListeners.map(listener => { | ||
return listeners.has(listener) && new Promise(resolve => { | ||
resolve(listener(eventData)); | ||
}).catch(hostReportError); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC I switched this to new Promise()
, but @dinoboff do you think we should use try-await-catch
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer new Promise()
in this case, but I don't mind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, neither do I.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should use try-await-catch.
- catch listeners’ errors. - let the host emit an unhandled rejection event for each listener error.
636e275
to
d08a21b
Compare
@dinoboff Interested in finishing this up? See https://github.com/sindresorhus/emittery/pull/19/files#r292604789. @novemberborn Other than the above, does this look good to you? |
emit()
wait for all listeners to settle
Ping :) |
Hey @sindresorhus, thanks a lot for this library it's being great using it. I came up with a different approach to avoid losing any of the erred promises. It's easy enough to implement in user-space (see the example code below), but I wanted to share it with you as an alternative idea for you to consider. It basically does the following:
This is our wrapper implementing this approach: const Emittery = require('emittery')
const VError = require('verror')
const EVENT_CALLBACK_ERROR = 'eventBus/callbackError'
const bus = new Emittery()
module.exports = {
emit: bus.emit.bind(bus),
clearListeners: bus.clearListeners.bind(bus),
on(eventName, eventCallback) {
bus.on(eventName, safelyWrapCallback(eventName, eventCallback))
},
onCallbackError(cb) {
this.on(EVENT_CALLBACK_ERROR, cb)
},
}
/**
* Wraps the callback in an async function which will not
* throw and will emit an EventBusCallbackError instead
*/
function safelyWrapCallback(eventName, cb) {
const callBackName = cb.name || 'anonymous callback'
return async function safelyWrappedCallback(payload) {
try {
return await Promise.resolve(cb(payload))
} catch (cause) {
const error = new VError(
{
name: 'EventBusCallbackError',
cause,
info: {
eventName,
callBackName,
},
},
'Listener on %s rejected: %s',
eventName,
callBackName,
)
if (eventName !== EVENT_CALLBACK_ERROR) {
// only emit for other eventNames to avoid recursion
bus.emit(EVENT_CALLBACK_ERROR, { error })
}
return error
}
}
} I'd love to hear your thoughts on this, and I'd be happy to contribute a PR. Thanks again! EDIT: sorry to hijack this PR, I realize it would have been more appropriate to open a new issue. |
@esclapes Would be great if you could move this into a new issue. |
Closing this PR for lack of response. #51 |
Currently,
emittery#emit
does settle early if one of the listener rejects. Either its documentation should be changed (if it's exclusively used to handle error, the behaviour is fine) or it first iterate over each listener result an wait for it to settle and reject with the first error at the end of the iteration.WIP: only implemented the test.