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

Question about the abilities of async iteration #61

Open
KilianKilmister opened this issue Jul 16, 2020 · 2 comments
Open

Question about the abilities of async iteration #61

KilianKilmister opened this issue Jul 16, 2020 · 2 comments

Comments

@KilianKilmister
Copy link

A few months ago I had to deal with large tree structures and started learning about stuff about Iteration and have been using it a lot since then (can't wait for the iterator-helper proposal).
And while reading up something aboud NodeJS-Streams i found this line in the docs: (section talking about their compatability from the docs)

With the support of async generators and iterators in JavaScript, async generators are effectively a first-class language-level stream construct at this point.

And after some learning experimenting i pretty much replaced using Stream-classes in favour of async-itterators/generators for all simpler use-cases, because I was never the biggest fan of the NodeJS-EventEmitter implementation.

I randomly stumbled over this project and quite like the things it does and the detailed explanations on the website taught me some new things. So i wanted to ask:
What are your thoughts on using modern async iteration to create an alternative EventEmitter with similiar capabilities?

Node already exposes to functions for working with promises and async iteration on EventEmitters:

function once (emitter: EventEmitter, event: string | symbol): Promise<any[]>
function on (emitter: EventEmitter, event: string | symbol): AsyncIterator<any[]>

I did some experimenting around creating such a class and i had some pretty good results, and ended up with quite simple code that implemented many of the safety features and other capabilities of the NodeJS-EventEmitter.

And now that i found this project i wanted to ask someone who knows a bit more about this topic than me.
The Pub/Sub sub-package already implements some similar things

@brainkim
Copy link
Member

What are your thoughts on using modern async iteration to create an alternative EventEmitter with similar capabilities?

Thank you for checking out repeaters, and thanks for the question. My current opinion is that I view most event to async iterator APIs with a little bit of suspicion. The reason is that the primary advantage of using a callback-based API over an async iterator-based API, is that with event systems, you have the option to synchronously dispatch the event. In other words, by the time your call to the emit or dispatchEvent methods completes, all listener callbacks will have been executed.

This is an incredibly useful invariant, and you can‘t achieve it when using async iterators. Async iterators are asynchronous at both ends, and you can’t from the producer of an event, ensure that all async iterator consumers resume synchronously, because this communication is mediated by the microtask queue. This means, that, for instance, if you had a theoretical async iterator which yielded DOM MutationRecords whenever the DOM was mutated, you could not rely on the DOM to be in a specific state at the time the mutation record was received. In general, the messages passed through async iterators can never reliably tell you the current state of your application, because your code which iterates over the async iterator will always work asynchronously.

This isn’t such a big deal for Node.js event emitters, which are simple abstractions, but the DOM event system has many more features, and things like event cancellation make it even harder to model the event system with async iterators, because you can’t cancel an event asynchronously. Calling ev.preventDefault outside of the synchronous dispatch of the event has no effect.

Also, I’m not sure these APIs are production-ready, insofar as any async iterator which doesn’t bound the limit to the number of events that can be enqueued is a memory leak waiting to happen. Note that none of the event listener methods ever seem to provide a hint as to how to deal with backpressure. What happens when you open an event async iterator but never collect any of the events?

Ultimately, there are always going to be situations where you might need your data sources to be listened to synchronously. Therefore, these days I’m thinking that you should always first start with an evented, synchronous abstraction before adding async iterators on top of it, because while it’s easy to go from sync to async (with Repeaters 😉), it’s impossible to go from async to sync. I’m even thinking about deprecating the pubsub package for this reason.

Hope this answers your question!

@KilianKilmister
Copy link
Author

I'm not very familiar with DOM-events and the browser-environment in general, as i almost exclusively work on the backend and every time i do work on something for the browser, i get scared off by the pure anarchy the browser-environment (or its common practises atleast) is compared to the more structured and elegant solutions usually found in NodeJS.
So i really apreciate the detailed explanation.

I haven't gotten a chance to use repeaters yet, as i just found them recently. But i'm looking forward to working with them.
I'm a big fan of the design desition of imitationg the Promise-constructor pattern. It really gives them a familiar feel and look. I'll remember this approach. Might be good for the things i'm doing.

I'll probably still work on a more modern EventEmitter construct (compared to the native node EE) that's a little more practical to use with async await and probably some other neatness.
The main problem i have with the vanilla implementation is that when you only need to wait for the next occurance of an event, using await once(EventEmitter, Event) feels weird inside a method of that same EE, and using this also prevents you from getting intellisense suggentions about which events are present. And there seems to be no way to implement that second one with typedefs, as different emitters have different amounts of overload signatures.
I know those are some pretty minor inconveniences, but it could be fun to work on an alternative.
You gave me some good things to look out for tho.

Note that none of the event listener methods ever seem to provide a hint as to how to deal with backpressure.

This is a very interesting thing to think about.

NOTE: NodeJS has an experimental implementation of the EventTarget Web API, but it's quite limited at the moment and it looks like its objects aren't meant to be creatable in user-land code

Hope this answers your question!

It does very much, thanks a lot.

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

No branches or pull requests

2 participants