Skip to content
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

fix(client): Rethrow async event emitter errors ASAP #1312

Merged
merged 1 commit into from
Jun 3, 2024

Conversation

msfstef
Copy link
Contributor

@msfstef msfstef commented Jun 3, 2024

Minor correction to the fix implemented in #1247

Reintroducing the queueMicrotask throwing approach to ensure errors are definitely not suppressed (e.g. they need to be thrown independently of all listener promises settling/fulfilling) and ideally if more than one of them occur, all of them are exposed to the global error handler. See discussion here #1247 (comment)

The queueMicrotask API seems to be widely supported by browsers and runtimes so I don't think there's a need to worry. The alternative is also to use setTimeout which is also widely supported, but that might cause the errors to be thrown significantly later than when they were emitted.

Copy link
Contributor

@kevin-dp kevin-dp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, left a stylistic comment.

@@ -146,7 +146,18 @@ export class AsyncEventEmitter<Events extends EventMap> {
// deep copy because once listeners mutate the `this.listeners` array as they remove themselves
// which breaks the `map` which iterates over that same array while the contents may shift
const ls = [...listeners]
const listenerProms = ls.map((listener) => listener(...args))
const listenerProms = ls.map(async (listener) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a bit of boilerplate here with the async function, await, and then surrounding it in try catch.
Would simplify it to:

ls.map((listener) =>
  listener(...args)
    .catch((err) =>
      queueMicrotask(() => throw err)
    )
)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor issue is that the listeners can be both sync and async (return type void | Promise<void>) so .catch can only conditionally be chained, which means that errors from synchronous listeners will be thrown immediately in the map and the ones from asynchronous listeners will be thrown after the queue is processed.

Not necessarily the worst case but with the try ... catch and await they are all handled the same way and all errors are bubbled up in the same way (although synchronous handlers will now be delayed a little bit because of await giving up control, but it's fine since it's an async event emitter)

Basically without the try ... catch we introduce the possibility of the queue not being fully processed and it getting interrupted by an error from a synchronous listener

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, let's keep it how it is 👍

@msfstef msfstef merged commit 189bd52 into main Jun 3, 2024
10 checks passed
@msfstef msfstef deleted the msfstef/improve-async-handler-error-throwing branch June 3, 2024 11:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants