Last active
January 13, 2024 17:02
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "events-polyfill", | |
"version": "0.1.0", | |
"type": "module", | |
"main": "events-polyfill.js", | |
"dependencies": { | |
"eventemitter3": "^5.0.1" | |
} | |
} |
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,
}),
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
sir how to apply the polyfill and Overwrite EventEmitter?