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

Is it possible to optionally "trampoline" subscriptions to be asynchronous? #74

Open
mmocny opened this issue Sep 29, 2023 · 3 comments

Comments

@mmocny
Copy link

mmocny commented Sep 29, 2023

My understanding is that .subscription callbacks are always dispatched synchronously as new data arrives, even when the data itself can arrive as a stream, asynchronously.

This is described as a feature and the primary use case was specifically for EventTarget support.

However, I also see in this example a (hypothetical?) alternative to .subscribe using .then syntax to compare Promise behaviour which would queue a microtask instead. My read is that .then isn't actually part of the Observable API proposal and that was just hypothetical-- is that correct?


All that makes sense to me-- however, I think there do exist very real use cases (see at bottom) for desiring that the dispatch of subscription calls would be truly asynchronous (i.e. a new macrotask not just microtask), and especially so for EventTarget.

Perhaps one way to accomplish this manually would be to:

observable.subscribe({
  next: async function() {
    await scheduler.yield();
    // continue...
  }
});

I am not sure if next: can accept and async function... but probably we don't even need to return the Promise since I doubt .subscribe will await it anyway?

Or perhaps there could be a helper to automate this, something like:

element.on('click').asyncify().subscribe({ ... })

Is this already an established pattern that exists?

However, I feel it may be enough important for EventTarget to warrant a subscription helper that is async-dispatch-by-default. Perhaps something like:

// This would call callback in a distinct macrotask... ideally after-next-paint when needed.
element.after('click').subscribe({ ... });

...and perhaps Task priority is also related. EventDispatch is typically very high priority, but perhaps observer callbacks should have the option to change their own priority?


The use case for requesting async dispatch of EventTarget subscription is for decoupling necessary effects which follow important interactions, from unnecessary effects which can be delayed until after next paint.

See the Optimize INP guide for examples of explicitly using yield points to manually to accomplish such patterns.

Today, the web platform does not support native "passive" event listeners which would dispatch callbacks asynchronously (perhaps it could) -- but I wondered if the Observer API already has solutions that would easily address this use case?

@mmocny
Copy link
Author

mmocny commented Sep 29, 2023

I just noticed this note:

observable/README.md

Lines 533 to 536 in 8d43ff4

Note that the operators `every()`, `find()`, `some()`, and `reduce()` return
Promises whose scheduling differs from that of Observables, which sometimes
means event handlers that call `e.preventDefault()` will run too late. See the
[Concerns](#concerns) section which goes into more detail.

In this case, I of course am asking for an opt-in mechanism, but it is interesting that this is already somewhat of an existing mechanism.

@mmocny
Copy link
Author

mmocny commented Sep 29, 2023

Somewhat off-topic but I also found this comment to suggest that it has indeed been common in the observable space generally to follow such a pattern

common case of awaiting each value from the observable, but processing them asynchronously

Though I am not sure if there exists a way to pipe + concatMap + sleep in the current proposal (I guess userland extensions would provide that?)

@mmocny
Copy link
Author

mmocny commented Oct 8, 2023

After some more reading of RxJS, I believe I am specifically asking for asyncScheduler as part of the larger scheduler feature set.

I suspect this is beyond scope of the proposal at the moment, but seems easy enough to polyfill by creating a custom operator (coincidentally, that example already does what I ask for).

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

1 participant