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

Drawbacks of when #190

Closed
noamr opened this issue Jan 10, 2025 · 12 comments
Closed

Drawbacks of when #190

noamr opened this issue Jan 10, 2025 · 12 comments

Comments

@noamr
Copy link

noamr commented Jan 10, 2025

Choosing EventTarget.when as the way to tie observables to event targets feels more modern than some other DOM APIs, however, I think it has some drawbacks, as opposed to a more conservative alternative, perhaps new ObservableEventListener({target, event, ...}) or some such.

The main drawback is that in order to extend ObservableEventListener, or change its semantics (e.g. the whole ref-counting hot/cold thing), when is going to require more options. The same if we ever extend the generic Observer constructor with more options.

On a more minor note, the when word doesn't fit grammatically with how it's used. It's more of an on. button.when("click", { capture: true}) doesn't sound correct. I get that there were many discussions about this in the past and on would have backwards-compat issues.

Perhaps new ObservableEventListener({ target: button, event: "click", capture: true }) is both extendable, discoverable, and consistent with how you construct existing similar platform primitives? (e.g. PerformanceObserver, ResizeObserver, IntersectionObserver).

Looking at https://wicg.github.io/observable/#dom-eventtarget-when, there's nothing to it except for constructing something and returning it, so perhaps that's how it should look like to the outside world?

Disclaimer: I wasn't here in past conversations so it's possible I'm missing something major.

@domfarolino we've discussed this offline, but wanted to have this documented here :)

@noamr noamr changed the title Drawbacks of `when1 Drawbacks of when Jan 10, 2025
@dariomannu
Copy link

dariomannu commented Jan 11, 2025

Hi @noamr, this makes total sense.

I would also add the ability to set up the listener in two steps, as follows:

// step 1:
const stream = new ObservableEventListener()
  .filter(conditions)
  .map(stuff)
;

// step 2:
eventTarget.when('click', stream, { capture: true });

// step 3:
stream.subscribe(sink);

This way frameworks and UI libraries could take care of and abstract away steps 2 and 3, leaving applications able to define the whole stream and its logic as step 1 in a component, before mounting (problem also surfaced in #186).

Additionally, in this form, the same stream could be reused and attached to multiple sources?

@dariomannu
Copy link

Perhaps const observable = element.observe(event, stream, options), instead of .when(...)?

That could also be paired with a convenient element.unobserve(stream), for those who haven't really bought into the AbortController...

@noamr
Copy link
Author

noamr commented Jan 11, 2025

Perhaps const observable = element.observe(event, stream, options), instead of .when(...)?

That could also be paired with a convenient element.unobserve(stream), for those who haven't really bought into the AbortController...

This still has the same issues as when, as described in the OP, albeit with a cleaner choice of name.

@noamr
Copy link
Author

noamr commented Jan 11, 2025

Hi @noamr, this makes total sense.

I would also add the ability to set up the listener in two steps, as follows:

// step 1:

const stream = new ObservableEventListener()

  .filter(conditions)

  .map(stuff)

;



// step 2:

eventTarget.when('click', stream, { capture: true });



// step 3:

stream.subscribe(sink);

This way frameworks and UI libraries could take care of and abstract away steps 2 and 3, leaving applications able to define the whole stream and its logic as step 1 in a component, before mounting (problem also surfaced in #186).

Additionally, in this form, the same stream could be reused and attached to multiple sources?

Maybe, but I would still avoid adding new functions to EventTarget which the whole DOM inherits from. I would prefer something like

observableListener.listen(target, event, options)

@ljharb
Copy link

ljharb commented Jan 11, 2025

Even if the only benefit was "avoid needing to use new", to me that more than sufficiently motivates when.

@noamr
Copy link
Author

noamr commented Jan 11, 2025

Even if the only benefit was "avoid needing to use new", to me that more than sufficiently motivates when.

So adding a property with a short common name to almost all DOM classes out there, including the global object, some of which might be overriding it somehow today (eg custom elements), and making the resulting class less extendable as a result is better than typing new because...?

@ljharb
Copy link

ljharb commented Jan 11, 2025

Note that I don't think that's the only benefit of when; i think that's the correct terminology and that it makes the mental model and googleability of the feature much simpler for users to learn.

But yes, because new (and inheritance and object instances) are in general terms very confusing concepts that cause most of the bugs in the JS world, and that if we can avoid having it in users' faces that it will make everything better.

@noamr
Copy link
Author

noamr commented Jan 11, 2025

Note that I don't think that's the only benefit of when; i think that's the correct terminology and that it makes the mental model and googleability of the feature much simpler for users to learn.

For users of Observable, maybe. But for the rest, e.g. ones that use existing libraries that have a different meaning/implementation of the word when (e.g. jQuery, Autobahn, testing libraries, internal libraries etc) it would create ambiguity between the platform when and the library's when.

But yes, because new (and inheritance and object instances) are in general terms very confusing concepts that cause most of the bugs in the JS world, and that if we can avoid having it in users' faces that it will make everything better.

This is an exaggeration.
Though something like Observable.from would still be a cleaner alternative than anythingInTheWorld.when.

@ljharb
Copy link

ljharb commented Jan 12, 2025

That ambiguity will be resolved, like with then, with all libraries aligning with the platform or dropping the term. That's fine.

It's not an exaggeration, although it is perhaps subjective.

@domenic
Copy link
Collaborator

domenic commented Jan 12, 2025

More important than avoiding new, is avoiding introducing a new class and concept (ObservableEventListener). A great part of the current design is that when() just returns a normal Observable, and so there's no new concept to learn.

As for the discussion about eventTarget.when() vs. Observable.from(eventTarget), the general pattern for class X is that we have methods or properties on other objects that return instances of X. We don't try to centralize all instances of creating X into the code for X itself. That somewhat defeats the purpose of reusable abstractions entirely, if we require all creators of an abstraction to be coded by the abstraction author.

@noamr
Copy link
Author

noamr commented Jan 12, 2025

Thanks for the discussion, happy to close this. I realized that this was a surface-level comment while something entirely different was bothering me here, I'll think it over and see if a meaningful discussion point comes up.

@noamr noamr closed this as completed Jan 12, 2025
@domfarolino
Copy link
Collaborator

Busy weekend here apparently! Thanks for the discussion everyone. A few closing thoughts below, but nothing substantive to add:

But yes, because new (and inheritance and object instances) are in general terms very confusing concepts that cause most of the bugs in the JS world, and that if we can avoid having it in users' faces that it will make everything better.

I'm pretty sympathetic to this, and the related:

That somewhat defeats the purpose of reusable abstractions entirely, if we require all creators of an abstraction to be coded by the abstraction author.

I feel the current design is the simplest, and regarding name choice, well, I would just point to the previous discussion where authors, framework folks, and others felt that landing on when() was a good choice.

On the other hand, I do feel like there's some merit to:

Perhaps new ObservableEventListener({ target: button, event: "click", capture: true }) is [...] consistent with how you construct existing similar platform primitives? (e.g. PerformanceObserver, ResizeObserver, IntersectionObserver).

On the surface, our current proposal does feel out of step with how other XObserver primitives are constructed. For example, these other primitives don't seem to follow what @domenic mentioned:

As for the discussion about eventTarget.when() vs. Observable.from(eventTarget), the general pattern for class X is that we have methods or properties on other objects that return instances of X.

I guess the difference is that the other XObservers only operate on a single type of object, so the ways in which their uses can be abstracted doesn't really burden the developer. I'm saying that if MutationObserver could be used to "observe" not only nodes, but lots of things like Promises, ServiceWorkerRegistration objects, and other things, then we'd maybe want methods on each of those objects to vend the XObserver, instead of forcing the user of XObserver to express that in the constructor somehow. That is, we wouldn't want to:

centralize all instances of creating X into the code for X itself. That somewhat defeats the purpose of reusable abstractions entirely, if we require all creators of an abstraction to be coded by the abstraction author.

So maybe the analogy between Observable and the other XObserver primitives is not as direct as it seems initially.

@domfarolino domfarolino closed this as not planned Won't fix, can't repro, duplicate, stale Jan 13, 2025
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

5 participants