rostacik
rostacik

Reputation: 23

how to correctly implement async event handlers in browser

I am struggling with this problem on an older project I have for a while. I also created a sample repo here but you maybe don't need it.

The problem is, I have more event handlers hooked on same elements or hooked to element up the chain where the event will bubble.

The problem is, that once the code of first handler encounters await (or yield when I build my code with typescript), next handler is fired. How can I synchronize execution to wait until everything in first handler finishes and only then continues to next event handler up the chain?

I saw some samples with "promisification of event emitter", but I am not in control of event object here, since it is created by browser runtime.

Also I have seen some RxJS hints, but could anyone give me a small sample, how he managed to fix the problem?

One small thing - sometimes I also need to await whole execution of another button click that might have chain of its own event handlers.

Upvotes: 2

Views: 3616

Answers (1)

Bergi
Bergi

Reputation: 664484

One might consider this a hack, but you could build a promise chain on the event object:

function delay(t) {
  return new Promise(resolve => { setTimeout(resolve, t); });
}
function handler(e) {
  e.innerColorChangeDone = Promise.resolve(e.innerColorChangeDone) // might be undefined
    .then(() => delay(100))
    .then(() => {
      this.style.backgroundColor = "#"+Array.from({length:3}, () =>
        Math.round(Math.random()*5+9).toString(16)
      ).join("");
      return delay(100);
    });
}
for (const el of document.getElementsByTagName("div"))
  el.onclick = handler;
div {
  padding: 1em;
  border: solid 1px #aaa;
  width: fit-content;
}
<div>
  <div>
    <div>
      <div>
        Click!
      </div>
    </div>
  </div>
</div>

The event object is the same, being passed to each of the handlers. If you want to do anything asynchronous that other (later) event handler will need to depend on, store a promise for your result on the event object. Then in the waiting handler, wait for that particular promise. You can also use multiple different promise chains, or ignore the promise if you want something in an outer handler to happen immediately.

In ES8 you might use async/await like this:

function handler(e) {
  e.innerColorChangeDone = (async () => {
    await e.innerColorChangeDone;
    await delay(100);
    this.style.backgroundColor = …
    await delay(100);
  })();
}

Upvotes: 2

Related Questions