RFC: EventController proposal #486
Replies: 4 comments 5 replies
-
I quite like this, especially the size and the typing of possible messages/events. Is there any way we could make this even simpler? I would be tempted to keep an array of events that have happened, and infer How would we handle implementation of event controllers not overriding each other on the export const CONTROLLERS = ['consent-management-platform', 'comments'] as const;
export type Controllers = typeof CONTROLLERS[number]; |
Beta Was this translation helpful? Give feedback.
-
I like the route of a global simple class that doesn't use observables - unless we want composability over streams but that could be done by the consumer. I agree that the naming is really important here and think there could be some improvement. NamingCould we reuse terminology that is already widespread for example:
My preference would be for Also should your examples use the opposite producer/consumer terminology? e.g. EventController.publisher('consentChange').onAfterFirst(()=> {
cmp.willShowPrivacyMessage().then(willShow => {
... becomes: EventController.subscribe('consentChange').onAfterFirst(()=> {
cmp.willShowPrivacyMessage().then(willShow => {
... FunctionalityI'm not sure we need the It would be good to see example use cases? |
Beta Was this translation helpful? Give feedback.
-
@mxdvl / @arelra I've simplified this somewhat. It's now just |
Beta Was this translation helpful? Give feedback.
-
This is super interesting! One thing: Is there a way to expose a more reacty version of this api? Most client side code in DCR is built using react and trying to integrate imperative listeners or promises into react code is famously hard and we don't want developers to have to repeatedly solve these problems each time they want to use this api. We could create a custom hook to do this in DCR but perhaps it's better (for other teams) if this was already defined and exported from commercial core? The AB test framework is an example for exporting both vanilla js and react versions of an api. What's wrong with listeners in react?The problem is react is declarative. It expects your code be able to be rerun (re-rendered) with no side effects. But listeners are imperative and should only be set once, so you need to manage this with guards. This is especially problematic when the listeners are tied to dom elements that have been rendered by react (you then need to start using refs) but it's also just generally confusing and makes it hard to reason about state. What's wrong with promises?As above, react is declarative and has its own built in methods for handling state changes. When you combine an async function that returns on its own timescale outside of react's lifecycle it can be hard to reason about changes. This can be ameliorated with async/await but in the words of Dan Abramov: "Async/await in components is a bugfest" |
Beta Was this translation helpful? Give feedback.
-
Updated Wed 9 Feb
I've updated the explanation, and simplified the API.
Updated Wed 10 Feb
Better use of generics? Possibly?
What is this?
It's a pattern, and a standard for how we can communicate between modules.
It's a simplified event-emitter/pub-sub with the addition that in addition to hooking into the next invocation as normal, you can also pull the last/latest value from it. This is useful particularly in the case of the CMP where many consumers want to know if consent has already been set, and if so what it is, and if not, a callback or promise for when it will be.
I've written this to support both callbacks or promises depending on what you need, since different parts of our code use both styles. (More about this below, but this isn't important, it's just something I added for fun. If the consensus is that this isn't a good idea we don't have to keep this 'feature', and can simplify it down to consistently just use callbacks or return promises).
I've not done this here, but the point is partially that by having a place where we define standard interface for these events, we can define the type of events and event data as narrowly as we like here.
The obvious use is for CMP consent change, but we could use it for changes of comment pages, or updates to the liveblog.
on
method receives a callback that is called on every future eventnext
If a callback is passed it's executed once, on the next eventIf no callback is passed it returns a promise that resolves on the next event.
after
If event has not previously been dispatched, this behaves the same asnext
.If event has previously occurred it resolves/calls back immediately with the last value.
dispatch
Used by the originator to actually publish the eventsHow is it used?
Example 1
boot.js currently does this:
The
let recordedConsentTime = false
stuff is here just becauseonConsentChange
happens every time consent changes, but we only care the first time.If we were to adopt this proposal it could be updated to look like this:
Example 2
We have a file called inital-consent-state in frontend. This proposal would let us replace it with this:
after
method, but here as I'm not passing it a callback so it returns a promise I canawait
. Bothafter
andnext
will return a promise if not passed a callback. The third subscriber event:on
only accepts a callback because it will perform the action for all future events (and a promise can only resolve once).Is this just observables but less flexible?
Yeah. But's like 800bytes gzipped, and does what we need? Maybe?
Is this just eventEmitter (aka mediator), but without off /remove listener events?
Basically? Frontend formally used eventEmitter for similar reasons. The problems it was there to solve haven't entirely gone away in DCR, so it makes sense to bring back some version of it. True this doesn't support off/remove listener, and we could add those if they're useful. We could also use the native
addeventlistener
API instead of keeping them in an array, under the hood. But I think those are implementation details I'm not that fussed about at this point.If we recognise that this is a use-case we have in our code base and this is a generalised pattern that could address it, I think the important bit / useful part here is the controller. It becomes the place we document / define the types of events and what kind of data they're expecting. Providing a clearer contract in this interface layer between modules that publisher events and those that listen for them.
Beta Was this translation helpful? Give feedback.
All reactions