Many of JavaScript's interfaces are event-based and not immediately compatible with async / await.
The EventTarget class could be extended to return a Promise or an async iterator.
Their syntax could resemble the syntax of addEventListener.
Usage
const myButton = document.querySelector( "button" );
//await a single event:
let nextClick = await myButton.listenForEvent( "click" );
console.log( "Got a click: ", nextClick );
//await a stream of events:
for await ( const click of myButton.streamEvents( "click" ) ) {
console.log( "Got another click: ", click );
}
Polyfill
listenForEvent( eventName ) would wrap a Promise around a single-use event listener.
Huh. Surprising. So all the interfaces with addEventListener, including window, aren't actually part of the ECMA spec? That's so fragmented...
Well, it is what it is.
Yep. Even Event is part of the DOM standard, not part of JavaScript.
One thing about the current polyfill is that it doesn't handle successive, synchronous dispatches. So if you were to
myButton.click()
myButton.click()
You'd only see the "Got another click: <event>" message once rather than the expected twice.
Also, a problem for these kinds of dispatches is that because events are mutated as they bubble through the DOM, the fact that the promise is delaying the execution of the code handling the event means the event state could be stale and values like currentTarget may not be what you expect by the time that code gets the event.
for await ( const click of myButton.streamEvents( "click" ) ) {
console.log( "Got another click: ", click.currentTarget === myButton );
}
// ...
myButton.addEventListener("click", (event) => {
console.log( "Got a callback click", event.currentTarget === myButton );
});
myButton.click()
// "Got a callback click: true"
// "Got another click: false"
I think it would be challenging to try to get promises to work well with the EventTarget API.
That's probably a bad idea. An async iterator runs only as fast as it is consumed, but events fire at any rate. It would be quite error-prone to write code like
for await (const event of document.body.streamEvents("scroll")) {
await delay(50);
console.log("Debounced scrolling: ", event);
}
console.log("Done"); // dead code?
since the async iterator would not be able to handle the backpressure. Should it just drop events? Should it buffer them? Neither is really desirable.
Using for await leads to users writing sequential code, addEventListener allows writing properly concurrent asynchronous code.
I think it would be challenging to try to get promises to work well with the EventTarget API.
And also @bergus my thought process was that it wouldn't matter. If the user spammed tons of clicks, obviously the page would not behave right.
But that was a very flawed assumption on my part. These problems are exactly what I've encountered trying to use async / await with WebSockets / WebRTC and something similar to this model. Missing events is absolutely unworkable with something like a handshake.
It's a moot point since I don't know HTML well enough at all to involve myself in its development. My intuition still tells me it's possible to wield onmessage with async / await. I hope to figure it out eventually.