Skip to content

Instantly share code, notes, and snippets.

@Sceat
Last active January 13, 2024 17:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sceat/e2c3b3969513bbd62c1dc2ba2a565fbf to your computer and use it in GitHub Desktop.
Save Sceat/e2c3b3969513bbd62c1dc2ba2a565fbf to your computer and use it in GitHub Desktop.
A robust chatGPT made implementation of nodejs Events.on and Events.once for the web
import EventEmitter from 'eventemitter3'
function create_queue() {
const events = []
return {
push(event) {
events.push(event)
},
shift() {
return events.shift()
},
get is_empty() {
return events.length === 0
},
iterate(callback) {
while (events.length > 0) {
const event = events.shift()
callback(event)
}
},
}
}
function is_event_target(emitter) {
return emitter && !!emitter.addEventListener
}
/** @param {EventEmitter | EventTarget} emitter */
function on(emitter, event_name, options = {}) {
const { signal } = options
if (signal?.aborted)
throw new DOMException('The operation was aborted.', 'AbortError')
const add_method = is_event_target(emitter) ? 'addEventListener' : 'on'
const remove_method = is_event_target(emitter) ? 'removeEventListener' : 'off'
const unconsumed_events = create_queue()
const unconsumed_promises = create_queue()
let finished = false
let error = null
const event_handler = (...values) => {
if (unconsumed_promises.is_empty) {
unconsumed_events.push(values)
} else {
const { resolve } = unconsumed_promises.shift()
resolve({ value: values, done: false })
}
}
emitter[add_method](event_name, event_handler)
const remove_event_listener = () =>
emitter[remove_method](event_name, event_handler)
const iterator = {
next() {
if (error) return Promise.reject(error)
if (!unconsumed_events.is_empty) {
const event = unconsumed_events.shift()
return Promise.resolve({ value: event, done: false })
}
if (finished) return Promise.resolve({ value: undefined, done: true })
return new Promise((resolve, reject) =>
unconsumed_promises.push({ resolve, reject }),
)
},
return() {
finished = true
remove_event_listener()
unconsumed_promises.iterate(({ resolve }) => {
resolve({ value: undefined, done: true })
})
return Promise.resolve({ value: undefined, done: true })
},
throw(err) {
finished = true
remove_event_listener()
error = err
unconsumed_promises.iterate(({ reject }) => reject(err))
return Promise.reject(err)
},
[Symbol.asyncIterator]() {
return this
},
}
if (signal) {
const on_abort = () => {
error = new DOMException('The operation was aborted.', 'AbortError')
iterator.return()
}
signal.addEventListener('abort', on_abort, { once: true })
// Override the return method to ensure abort listener is removed on iterator cleanup
const originalReturn = iterator.return
iterator.return = () => {
signal.removeEventListener('abort', on_abort)
remove_event_listener()
return originalReturn.call(iterator)
}
}
// Close events
if (options.close) {
options.close.forEach(close_event => {
if (is_event_target(emitter))
emitter.addEventListener(close_event, () => iterator.return())
else emitter.on(close_event, () => iterator.return())
})
}
return iterator
}
const once = (emitter, event_name, options = {}) =>
new Promise((resolve, reject) => {
const remove_method = is_event_target(emitter)
? 'removeEventListener'
: 'off'
const on_abort = () => {
emitter[remove_method](event_name, resolve)
reject(new DOMException('The operation was aborted.', 'AbortError'))
}
if (options.signal) {
if (options.signal.aborted) on_abort()
else options.signal.addEventListener('abort', on_abort, { once: true })
}
emitter.once(event_name, (...values) => {
if (options.signal) options.signal.removeEventListener('abort', on_abort)
resolve(values)
})
})
export { EventEmitter, on, once }
export default EventEmitter
{
"name": "events-polyfill",
"version": "0.1.0",
"type": "module",
"main": "events-polyfill.js",
"dependencies": {
"eventemitter3": "^5.0.1"
}
}
@eL1x00r
Copy link

eL1x00r commented Jan 12, 2024

sir how to apply the polyfill and Overwrite EventEmitter?

@Sceat
Copy link
Author

Sceat commented Jan 12, 2024

I personally use 'vite-plugin-node-polyfills'

      nodePolyfills({
        // To add only specific polyfills, add them here. If no option is passed, adds all polyfills
        include: ['stream', 'events', 'path', 'timers/promises', 'util'],
        overrides: {
          events: 'events-polyfill',
        },
        // Whether to polyfill `node:` protocol imports.
        protocolImports: true,
      }),

@eL1x00r
Copy link

eL1x00r commented Jan 13, 2024

in case of monkeypatching inside the js file?

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