Skip to content

Instantly share code, notes, and snippets.

@Lcfvs
Last active September 28, 2021 11:41
Show Gist options
  • Save Lcfvs/bc70fbfc309167b1cfecb9ad924dd7b3 to your computer and use it in GitHub Desktop.
Save Lcfvs/bc70fbfc309167b1cfecb9ad924dd7b3 to your computer and use it in GitHub Desktop.
An iterable event handler

EventHandler

Usage

const handler = new EventHandler(document.body, 'click')

setTimeout(() => handler.disconnect(), 5000)

for await (const event of handler.getIterator()) {
  console.log(event)
}

console.log('end')
const registry = new FinalizationRegistry(handler => handler.disconnect())
class EventHandler {
#consumed = false
#options = null
#prevent = false
#promises = []
#resolve = null
#ref = null
#type = null
#enqueue = event => {
if (this.#resolve) {
this.#resolve(event)
this.#next()
}
}
#next = () => this.#promises.push(new Promise(this.#wait))
#wait = resolve => this.#resolve = resolve
constructor (target, type, { prevent = false, ...options } = {}) {
this.#ref = new WeakRef(target)
this.#type = type
this.#options = options
this.#prevent = prevent
this.#next()
target.addEventListener(type, this, options)
registry.register(target, this)
}
disconnect () {
this.#ref.deref()?.removeEventListener(this.#type, this, this.#options)
this.#resolve()
this.resolve = null
}
async* getIterator () {
if (this.#consumed) {
throw new Error('Unable to create an additional iterator')
}
this.#consumed = true
while (this.#resolve || this.#promises.length) {
const value = await this.#promises.shift()
if (value === undefined) {
break
}
yield value
}
}
async handleEvent (event) {
if (this.#prevent) {
event.preventDefault()
}
this.#enqueue(event)
}
}
@WebReflection
Copy link

I think asynchronous events aren't a good idea ... as example, you can't stopPropagation or even stopImmediatedPropagation there, if these actions are done in a tick where the event already bubbled the whole document, right?

@Lcfvs
Copy link
Author

Lcfvs commented Sep 28, 2021

@WebReflection: Yeah, maybe, but we can also some options to automate them, like how I made it for the prevent :)

@WebReflection
Copy link

@Lcfvs sure thing, all I am saying is that events that are expired are useless. Also having target, coords, etc ... that's not how events work, and the reason in SW you need to explicitly waitUntil or the event is a dead one, which is not really good.

@Lcfvs
Copy link
Author

Lcfvs commented Sep 28, 2021

@WebReflection I see your point but, imho, there is also some cases which doesn't have an event expiration, like:

  • to be able to make a cooldown, awaiting some other things but keeping the event order
  • to easily make a repeatable step system like this
const step1 = new EventHandler(target1, type, options).getIterator()
const step2 = new EventHandler(target2, type, options).getIterator()
const step3 = new EventHandler(target3, type, options).getIterator()
// ...

for await (const event of step1) {
  await step2.next()
  await step3.next()
  // ...
}

The idea isn't to replace anything, just to have an easy way to do that, solving the same caveats than I presented on the MO proposal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment