Skip to content

Conversation

jakearchibald
Copy link
Contributor

@jakearchibald jakearchibald commented Sep 17, 2025

I'm proposing this following discussions on ProgressEvent in webmachinelearning/translation-api#61.

I wanted to provide reasoning and clarity for this rule, assuming I understand its intent correctly.


Preview | Diff

What is more often implied by "asynchronous event" is to defer firing an event.

<h3 id="state-and-subclassing">Use plain {{Event}}s for state</h3>
<h3 id="state-and-subclassing">Put state on {{Event/target}} objects rather than {{Event}}s</h3>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous heading seemed… incomplete.

@lukewarlow
Copy link

Thanks for this I meant to raise an issue that examples should be added. I think an example that follows the principle is good but an example that doesn't follow it, and what it should have looked like would be useful too.

index.bs Outdated

Defining an interface also exposes it on the global scope, allowing for the specification of static methods.
For example, the `canParse()` static method of the URL interface.
For example, the `canParse()` static method of the URL interface.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated change, but it's something many editors do automatically. I can remove it, but I imagine others will run into the same thing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are not the only one. We should maybe lint for trailing whitespace.

index.bs Outdated
Comment on lines 2593 to 2594
Instead, all state should have been on the {{XMLHttpRequest}} object,
and the {{XMLHttpRequestEventTarget/progress}} event should have been a simple {{Event}}.
Copy link
Member

@annevk annevk Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I agree with this principle. This assumes that tracking this information is free. I don't think it necessarily is. And with the current design only those interested in the information pay for having it tracked.

(This doesn't mean that FetchObserver/FetchMonitor can't expose the information directly, because obtaining such an object could be considered the opt-in.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair.

Copy link
Contributor

@martinthomson martinthomson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is way better than what we had. Thanks for doing this. I've made a few suggestions, but feel free to dismiss them.

and use events to signal updates to that state.

It's usually not necessary to create new subclasses of {{Event}}.
<div class="note">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be a note?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it to separate the rule from the explanation of the rule, since the explanation is non-normative. But this spec doesn't have such a strict divide between normative and non-normative. But I don't mind either way, happy to go with whatever you think.

jakearchibald and others added 2 commits September 18, 2025 11:23
Co-authored-by: Martin Thomson <mt@lowentropy.net>
@jakearchibald
Copy link
Contributor Author

I've removed the XHR counter-example based on @annevk's feedback. I couldn't immediately think of another counter-example.

Happy for this to land once others are.

@annevk
Copy link
Member

annevk commented Sep 18, 2025

I think the XMLHttpRequest example showed a problem with this principle as a whole. That it doesn't properly consider the engineering trade-offs involved. Whether you expose state on the EventTarget or the Event is really about the purpose of the API, likelihood of the state being needed in all usage scenarios, cost to users, etc.

@jakearchibald
Copy link
Contributor Author

My assumption was that any rules in this doc can be broken if it can be demonstrated that the alternative is better for users/developers in a given case.

@annevk would it help if I added a note recognising the case where the event listener can be used as an opt-in to internal processing? Although, as we see in the fetch case, there are other ways to handle the opt-in.

@annevk
Copy link
Member

annevk commented Sep 18, 2025

Yeah, maybe something that hints at the fact that for potentially costly auxiliary state there is a real trade-off to be made here.

While principles can be broken, generally they point out the one correct way to do something and folks might well take away from that they can never use state on events.

@jakearchibald
Copy link
Contributor Author

@annevk PTAL

Comment on lines +2587 to +2589
Although, other patterns should be considered,
such as retuning an {{EventTarget}} from a function,
where the function call is a similar signal of interest.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that I'm following this sentence. I don't know what "retuning" means in this context.

Do you mean that people might want to consider adding a function to EventTarget to get the state instead? Or are you saying that you might add a function that flips the EventTarget into a mode that tracks the state, so that the cost is only paid when the state is needed?

It would really help if there was a concrete example of this problem. I'm having a little trouble coming up with one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling the function returns the EventTarget, at which point the implementation will have to track the state (if it's exposed on the target instead of the event).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, so instead of something like:

self.pageDataUsage.addEventListener('change', () => {
  console.log(self.pageDataUsage.bytesReceived);
});

…where self.pageDataUsage.bytesReceived will need to be continually updated so it can be synchronously accessed by any code running in the doc/worker, you'd do something like:

self.pageDataUsage.addEventListener('change', (event) => {
  console.log(event.bytesReceived);
});

…where the data is only available to listeners of the change event, so if there are no listeners, the underlying operation to get bytesReceived doesn't need to happen. Or, alternatively, "return an {{EventTarget}} from a function":

const pageDataUsage = await self.monitorDataUsage();

pageDataUsage.addEventListener('change', () => {
  console.log(pageDataUsage.bytesReceived);
});

In this case, it's the call to monitorDataUsage() that signals interest, and GC of pageDataUsage will signal disinterest.

The second model gives you an object representing current state that can be passed around.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retuning is a typo in the PR. I'll fix that once we figure out the right wording.

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

Successfully merging this pull request may close these issues.

4 participants