Re: [whatwg/dom] Improving ergonomics of events with Observable (#544)

Hey everyone, I know this thread has been quiet for a while, but I've been looking at this proposal from an implementer perspective and I have a few questions, some of which stem from the fact that I think I only recently wrapped my head around the desired semantics here, so bear with me :)

1. **Why are Observables needed? Why is synchronous delivery important?**
    - I understand that the big difference between Observables and things like Streams / async iterators is:
        - [Push vs. Pull](https://github.com/whatwg/dom/issues/544#issuecomment-352509476) (yes I read @domenic's [SO answer](https://stackoverflow.com/questions/39439653/events-vs-streams-vs-observables-vs-async-iterators/47214496#47214496))
        - Inherently multi-consumer
        - Synchronous delivery
    - At the risk of sounding dumb, how critical are some of these constraints? I'm trying to figure out why an Observable's magic cannot be recovered by composing together other things like Streams, when you can use `tee()` on Streams to recover the multi-consumer behavior and generally [build](https://stackoverflow.com/questions/39439653/events-vs-streams-vs-observables-vs-async-iterators/47214496#47214496:~:text=To%20build%20push%20on%20top%20of%20pull%2C%20constantly%20be%20pulling%20from%20the%20pull%20API%2C%20and%20then%20push%20out%20the%20chunks%20to%20any%20consumers.) "Push" systems out of "Pull" systems. That leaves the synchronous delivery aspect, and I'd love to understand why it's important. When trying to figure this out, I was directed to https://surma.dev/things/streams-for-reactive-programming/ which described Surma's toy `observables-with-streams` library. Naturally, the biggest difference between his Observables & true Observables is the [**synchronous delivery**](https://surma.dev/things/streams-for-reactive-programming/#:~:text=To%20put%20it%20in%20a%20sentence%3A%20RxJS%20observables%20deliver%20their%20values%20synchronously%20to%20subscribers.%20Streams%20run%20their%20processing%20steps%20in%20a%20microtask%2C%20making%20them%20asynchronous.) of data. I was surprised to find that it didn't cause any problems with the simple web app he wrote with his library, but I'd love to understand why today's existing, complex use-cases of real Observables could not tolerate that behavior, and what's so important about synchronous delivery?
2. **Is "subscriber" the same as "observer"?**
    - It seems like it, but the argument in the Observable constructor's callback is usually called `subscriber` (when we call `subscriber.next(foo)`), yet the thing passed in to the `Observable#subscribe()` method is called an Observer.
I know that these do not reference the same underlying JS objects, but conceptually-speaking are these the same thing? Or is there another actor involved here called `Subscriber` that I'm missing?
3. **How big of a concern really, are the Promise-ifying APIs on Observable?**
    - Much ink has been spilled ([c](https://github.com/whatwg/dom/issues/544#issuecomment-351604037), [c](https://github.com/whatwg/dom/issues/544#issuecomment-351722701), [c](https://github.com/whatwg/dom/issues/544#issuecomment-351734439), [c](https://github.com/whatwg/dom/issues/544#issuecomment-351779661), [c](https://github.com/whatwg/dom/issues/544#issuecomment-351788994), [c](https://github.com/whatwg/dom/issues/544#issuecomment-351788994), [c](https://github.com/whatwg/dom/issues/544#issuecomment-352509476)) about how the microtask queue is consulted synchronously after browser-initiated event callbacks, but not for JS-initiated ones. The consequence is that JS-initiated events cannot be canceled (among other things) from within Promise callbacks when Promise-ifying an Observable's result (is "result" the right word?).
    - @jakearchibald and others mention that this is kind of a [big deal](https://github.com/whatwg/dom/issues/544#issuecomment-351734439). Obviously by implementing Observables in the browser itself, we'd be opening up their usage (and their Promise-ifying APIs) to people unfamiliar with Observables today who might find themselves a new footgun. But to estimate how much of a footgun it is, I'm interested in hearing if the reactive JS community has ever seen this as a footgun? Is this one of those things where everyone getting started with Observables has that moment where they say "Ah, I too used to get tripped up when trying to cancel my events from Promise-ifying Observable methods, until I figured it out". That's a bit contrived of course, but I'm trying to figure out if this has generally been a gotcha for newcomers or not?
    - I ask because we're fortunate to have a robust set of community-curated Observable implementations in userland that already have this potential issue that the browser would not be introducing. Maybe we can estimate how much of a gotcha this might be, by learning how much of a gotcha it already is.
4. **How should I think about [whatwg/dom#544](https://github.com/whatwg/dom/issues/544) vs [tc39/proposal-observable#201](https://github.com/tc39/proposal-observable/issues/201)** (per [#issuecomment-541763071](https://github.com/whatwg/dom/issues/544#issuecomment-541763071))?
    - I apologize for not having read the entire https://github.com/tc39/proposal-observable/issues/201 thread yet. **Is the ideal proposal/API the old one, or the "simplified" one?** What I'm getting at is: was the 2019 simplification a last-ditch effort to try and get it pushed through TC39, that made API compromises in exchange for process support, or is it a strict improvement that we'd pursue if starting over from scratch today with unlimited implementer/standards support?
    - There is some discussion in the https://github.com/tc39/proposal-observable/issues/201 simplification about how it's better suited for the "firehose" problem where an observable synchronously (I guess that goes without saying) produces a bunch of values that the Observer can luckily stop, given a token-based cancellation mechanism. How coupled is (**a**) the simplification proposal with (**b**) this cancellation mechanism? Are they orthogonal? Can the initial API proposal here just have the Observable constructor's subscribe function take in something like an AbortSignal that you pass in when calling `subscribe()`? I guess it might look like:
        ```js
        const obs = new Observable(subscriber => {
          for (i = 0; i < 9999999; ++i) {
            subscriber.next();

            if (subscriber.signal.aborted)
              break;
          }
        });

        AbortController controller;
        obs.subscribe({
          next: val => {
            if (val > 100)
              controller.abort();
          },
          signal: controller.signal,
        });
        ```

        I'm just trying to tease out the cancelation vs the API simplification, and see if they are decoupled at all.

-- 
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/dom/issues/544#issuecomment-1430690548
You are receiving this because you are subscribed to this thread.

Message ID: <whatwg/dom/issues/544/1430690548@github.com>

Received on Wednesday, 15 February 2023 03:12:27 UTC