Closed bmeck closed 1 month ago
Update, I've put an X poll out for this: https://x.com/domfarolino/status/1815458583334842691 to get a feel for how the community feels.
I'm partial to when
. I don't like verbs like listen
or observe
because they sound like they start the subscription, although observe
isn't that bad.
I'd consider how it reads in composition like:
element.when('mousedown')
.switchMap(start => document.when('mousemove')
.map(current => [
current.clientX - start.clientX,
current.clientY - start.clientY,
])
.takeUntil(document.when('mouseup'))
.subscribe()
Update, I've put an X poll out for this: https://x.com/domfarolino/status/1815458583334842691 to get a feel for how the community feels.
I would like to vote .listen() in that reply.
I don't like verbs like
listen
orobserve
because they sound like they start the subscription
I have the same feeling, but I feel like a noun like .events(...)
or similar makes more sense.
I'd still push on on
, and keep when
in our back pocket though.
Sadly I think we have to call time of death on on()
— I don't think the conflict is something we can overcome.
In the poll .observe()
is winning, but I'm sympathetic to the concern of using a verb (like .observe()
) which sounds very active, even though it just returns a "lazy" Observable.
Now I'm trying to see if the thoughts in https://github.com/WICG/observable/issues/72 will influence our decision at all. If we give *Observer
APIs the ability to vend Observables in the future via an overloaded .observe()
method, then does that mean we should also go with .observe()
for EventTarget
? Or is EventTarget
special for some reason.
Now I'm trying to see if the thoughts in #72 will influence our decision at all. If we give
*Observer
APIs the ability to vend Observables in the future via an overloaded.observe()
method, then does that mean we should also go with.observe()
forEventTarget
? Or isEventTarget
special for some reason.
I love the holistic thinking here.
Let's make it a bit more concrete. If we used observe()
for both with the static method option from #72 then we'd have:
// (1)
imageElement.observe("load").subscribe(...);
ResizeObserver.observe(imageElement).subscribe(...);
If we used observe()
for EventTarget
with the instance method version from #72:
// (2)
imageElement.observe("load").subscribe(...);
imageElement.observeResizes().subscribe(...);
If we use when()
for EventTarget
and observe()
for the static method option from #72 then we'd have:
// (3)
imageElement.when("load").subscribe(...);
ResizeObserver.observe(imageElement).subscribe(...);
If we use when()
for EventTarget
and the instance method version from #72:
// (4)
imageElement.when("load").subscribe(...);
imageElement.whenResized().subscribe(...);
Overall I like (2) and (4) the most. It feels like a great unification of events and XObservers, where the XObservers just need slightly more specialized methods instead of the generic event-name-taking method. This also ties into the discussion in #72 about how maybe these methods might need to return subclasses.
(1) is also reasonable, although it might be a bit confusing that the arguments to observe()
are so different.
I think (3) feels bad, but concretely wouldn't be the worst thing. It just makes the APIs feel completely separate. Which, to be fair, they kind of are; they just share a return type.
Of all the alternatives discussed, I'm +1 for when
or observe
. Of the options presented by @domenic, I'm partial to (2) and (4) also, tho I do also like the symmetry of (1).
I thought about this a bit more, and I think the visual symmetry of (1) is misleading. More generally, I think the symmetry of having a pre-existingXObserver#observe()
instance method and either EventTarget#observe()
instance method or XObserver#observe()
static method is misleading.
It means:
EventTarget
and XObserver
have instance methods that share a name but have completely different behavior (one is passive and the other is active)XObserver
itself has two "different" methods with the same name: one static and passive, one instance and active. But both by the same name!That worries me, and pushes me to option (3) or (4) — when()
. Option (4) looks the cleanest, but I'm trying to think through how much of a limitation it is that Element#whenResized()
produces an RO Observable only for the element it's called on.
Let's keep going with the ResizeObserver
-specific example. Today, when you use ResizeObserver
, you create one with a single potentially massive and complicated callback, and then you can call observe()
any number of times with different target elements. The same shared callback is used for processing all resizes, for all elements being observed, and presumably there is some (performance?) value to that. But with Element#whenResized()
and no work done to ResizeObserver
itself, you have to call el.whenResized().subscribe(massiveComplicatedCallback)
on many elements, and have potentially many copies of that callback floating around, with many different internal ResizeObservers under the hood. That's a lot less shared infrastructure, but maybe that's OK.
It was out of hope for "sharing more stuff" that I mentioned an Observable-returning method on XObserver
that's not static. I envisioned something like new ResizeObserver(massiveCallback).whenResized(element).subscribe()
, but hadn't thought it through — I don't think this works at all since the callback would need access to subscriber
to plumb values downstream. So I think the only way to do it would be to add a static method on XObserver
which is option (3), and doesn't seem all that useful. It takes me to @domenic's comment in https://github.com/WICG/observable/issues/72#issuecomment-2246837210:
An XObserver with no callback is basically storing no state so it doesn't really make sense to use instance methods.
... an argument I might extend, to apply to static methods too. That is, ResizeObserver.whenResized().subscribe(massiveCallback)
doesn't seem any more useful than element.whenResized().subscribe(massiveCallback)
. I guess the only benefit is that shares the same internal ResizeObserver under the hood. (That, and for MutationObserver we get to keep takeRecords()
where it is.)
All of that is to say that at least on this thread, I think I feel settled on when()
. I think we need to spend more time thinking about integration with other APIs in https://github.com/WICG/observable/issues/72, but I think we have enough to go off of here to make the decision on when()
in the meantime, regardless of how the integration details look. Thoughts?
Thank you for considering these concerns :heart:
Lots of libraries and runtimes use
on()
as a generic event handling mechanism. This name might cause problems for them updating and as such cause unnecessary friction. It would be good to see if there is another name that is less prevalent in the same design space that wouldn't collide with Cloudflare Workers, Bun, Node.js, etc. and the libraries written for them (Note: all of these have some form of EventTarget, but often mix with EventEmitter'son
in the wild or for backwards compatibility).