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)
}
}
@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