# Event async / await

**URL:** <https://es.discourse.group/t/event-async-await/1618>\
**Category:** 💡 Ideas\
**Tags:** proposal\
**Created:** [February 18, 2023, 2:19pm UTC](https://es.discourse.group/t/event-async-await/1618 "2023-02-18T14:19:34Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![0X-JonMichaelGalindo](https://avatars.discourse-cdn.com/v4/letter/0/4af34b/32.png) [@0X-JonMichaelGalindo](https://es.discourse.group/u/0X-JonMichaelGalindo)\
**Post date:** [February 18, 2023, 2:19pm UTC](https://es.discourse.group/t/event-async-await/1618/1 "2023-02-18T14:19:34Z")

</div>

Many of JavaScript's interfaces are event-based and not immediately compatible with `async / await`.  
The [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) class could be extended to return a Promise or an async iterator.  
Their syntax could resemble the syntax of `addEventListener`.

#### Usage

```javascript
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.

```javascript
EventTarget.prototype.listenForEvent =
    function( eventName ) {
        return new Promise( resolve => {
            const asyncEventListener = event => {
                this.removeEventListener( eventName, asyncEventListener );
                resolve( event );
            };
            this.addEventListener( eventName, asyncEventListener );
        } );
    }

```

And the async iterator generator `streamEvents` would yield a stream of those Promises:

```javascript
EventTarget.prototype.streamEvents =
    async function*( eventName ) {
        while( true ) {
            yield await this.listenForEvent( eventName );
        }
    }

```

With these two functions, all interfaces based on `EventTarget` would become compatible with `async` / `await` syntax.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [February 18, 2023, 10:10pm UTC](https://es.discourse.group/t/event-async-await/1618/2 "2023-02-18T22:10:31Z")

</div>

EventTarget isn't part of the language, it's part of HTML (and presumably WinterCG as well), so you'd need to ask in one of those places about it.

---

<div class="post-metadata">

**Author:** ![0X-JonMichaelGalindo](https://avatars.discourse-cdn.com/v4/letter/0/4af34b/32.png) [@0X-JonMichaelGalindo](https://es.discourse.group/u/0X-JonMichaelGalindo)\
**Post date:** [February 18, 2023, 10:31pm UTC](https://es.discourse.group/t/event-async-await/1618/3 "2023-02-18T22:31:57Z")

</div>

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`](https://dom.spec.whatwg.org/#interface-event) is part of the DOM standard, not part of JavaScript.

---

<div class="post-metadata">

**Author:** ![senocular](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/senocular/32/642_2.png) [@senocular](https://es.discourse.group/u/senocular)\
**Post date:** [February 18, 2023, 11:42pm UTC](https://es.discourse.group/t/event-async-await/1618/4 "2023-02-18T23:42:11Z")

</div>

One thing about the current polyfill is that it doesn't handle successive, synchronous dispatches. So if you were to

```javascript
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.

```javascript
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.

---

<div class="post-metadata">

**Author:** ![bergus](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bergus/32/152_2.png) [@bergus](https://es.discourse.group/u/bergus)\
**Post date:** [February 19, 2023, 12:25am UTC](https://es.discourse.group/t/event-async-await/1618/5 "2023-02-19T00:25:03Z")

</div>

> [@0X-JonMichaelGalindo](#):
>
> `listenForEvent( eventName )` would wrap a Promise around a single-use event listener.

You can do that even simpler, see e.g. [ecmascript 6 - Pausing javascript async function until user action - Stack Overflow](https://stackoverflow.com/a/45356249/1048572) :

```js
EventTarget.prototype.listenForEvent = function(type) {
  return new Promise(resolve => {
    element.addEventListener(type, resolve, {once: true});
  });
};

```

> [@0X-JonMichaelGalindo](#):
>
> And the async iterator generator `streamEvents` would yield a stream of those Promises:

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

```javascript
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.

Btw, there is (do I need to say: was?) [GitHub - tc39/proposal-observable: Observables for ECMAScript](https://github.com/tc39/proposal-observable) which proposed a proper abstraction for push-based event sources.

---

<div class="post-metadata">

**Author:** ![0X-JonMichaelGalindo](https://avatars.discourse-cdn.com/v4/letter/0/4af34b/32.png) [@0X-JonMichaelGalindo](https://es.discourse.group/u/0X-JonMichaelGalindo)\
**Post date:** [February 19, 2023, 1:10pm UTC](https://es.discourse.group/t/event-async-await/1618/6 "2023-02-19T13:10:26Z")

</div>

> 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.

---

<div class="post-metadata">

**Author:** ![coolCucumber-cat](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/coolcucumber-cat/32/1892_2.png) [@coolCucumber-cat](https://es.discourse.group/u/coolCucumber-cat)\
**Post date:** [October 23, 2023, 4:08pm UTC](https://es.discourse.group/t/event-async-await/1618/7 "2023-10-23T16:08:13Z")

</div>

No, because they don't have anything to do with JavaScript, just the browser. Other JavaScript runtimes like Node, Deno, Bun, don't have these.
