-
Notifications
You must be signed in to change notification settings - Fork 131
Description
Adding respondWith to the SubmitEvent contract means that SubmitEvent is no longer an "event" in the traditional software engineering sense IMHO.
Eventing systems have a few notable constraints which make them "events":
- The event dispatcher does not have direct knowledge of the event handlers.
- Multiple event handlers can handle the same event.
- Ordering of event handler execution is unpredictable.
EventTarget satisfies all these constraints in the general sense, but respondWith breaks this by creating a singular communication channel from the handler back to the event dispatcher. This is generally discouraged in the design of eventing systems, but can work in limited circumstances (aggregating all the responses to a given event for example). The simplest way I can demonstrate this is to have two handlers with respondWith.
<form>
<input type="text" name="user-name">
<input type="text" name="company-name">
<button type="submit">Submit</button>
</form>myForm.addEventListener('submit', (evt) => {
const userName = new FormData(evt.form).get('user-name');
const user = new User(userName);
console.log('Created user!', user);
if (evt.agentInvoked) {
evt.respondWith(Promise.resolve(`Created user: ${user.name}`))';
}
});
myForm.addEventListener('submit', (evt) => {
const companyName = new FormData(evt.form).get('company-name');
const company = new Company(companyName);
console.log('Created company!', company);
if (evt.agentInvoked) {
evt.respondWith(Promise.resolve(`Created company: ${company.name}`))';
}
});Both of these handlers are correct in isolation. One creates a user, the other creates a company, and both want to tell any submitting agent about it. But the agent has no way of accepting two different responses and unifying them together. The best it can is concatenate the two, but then which one goes first? If one says "Error" and that goes first, it might imply to the agent that the second string after it also represents a failure. The agent could error and reject the second call, but which call comes first is ultimately unpredictable. Event handlers as an architectural pattern have no way of collaborating to resolve this discrepancy. It's arguably incorrect for any handler to call respondWith because it cannot guarantee that some other handler won't call it with its own response.
The only real solution here is to reject the event architecture altogether and create one dedicated submit handler for both actions. In practice, this is what most developers do, and I think it is the right design at the application level. But I think that's indicative that an event is the wrong architecture for what we're trying to achieve with respondWith.
What we actually want is for the agent to tell the page it is submitting a form and let that page respond with a single, authoritative response. What we want isn't an event, it's just a function call! Consider this straw proposal:
interface HTMLFormElement {
submitTheFormFromAnAgent?: () => Promise<void>;
}
const myForm = document.querySelector('form')! as HTMLFormElement;
myForm.submitTheFormFromAnAgent = async () => {
// ...
return 'Created user ${user.name} and company ${company.name}';
};I think this is closer to the actual architecture we want, because it enforces no more than one handler must exist at any given time. Any code which wants to assign to this value has to understand that this is an exclusive capability it holds. It can check if any existing code already claimed this handler and know that reassigning it will prevent that other handler from running. From the agent's perspective, it triggers one submission and gets back one response, exactly as it expects.
I recognize that this is perhaps a criticism of the original submit event as much as it is of the newly proposed respondWith. I'm not entirely convinced it's reasonable to have multiple "submit" handlers in the first place, even independent of WebMCP. But ultimately we need to try to fit this API within the browser we have, not necessarily the browser we want, and if SubmitEvent.prototype.respondWith is the best of unifying the existing web API with new WebMCP, then I do understand that constraint. I just filed this to highlight that I believe an event is the wrong tool for a mechanism which demands a single authoritative response and I would love to see the spec consider other options.
Also yes, I am aware that sending messages back to the event dispatcher is an existing pattern in the web ecosystem with APIs like Event.prototype.preventDefault, Event.prototype.stopPropagation, and the obvious parallel FetchEvent.prototype.respondWith. I think these all have related issues (though FetchEvent is the worst offender IMHO). I mainly want to make sure we're thinking through whether respondWith is a good design for this API rather than adopting prior patterns just because they exist.