Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EventEmitter realisation #279

Open
wants to merge 25 commits into
base: ee
Choose a base branch
from
Open

EventEmitter realisation #279

wants to merge 25 commits into from

Conversation

serrhiy
Copy link

@serrhiy serrhiy commented Jan 23, 2025

Sorry if I messed up with the PR design, this is my first time. I will be glad to receive your criticism

Example:
const ee = new EventEmitter();

const fn1 = () => console.log('first');
const fn2 = () => console.log('second');

ee.once('data', fn1);
ee.once('data', fn2);
ee.off('data', fn1);
ee.emit('data');

Output:
first
second

As a solution I store seperatly functions added via once and on.
Example:
const ee = new EventEmitter();
setTimeout(() => ee.emit('data'), 3000);
ee.toPromise('data').then(() => console.log('Event emited'));
Example:
(async () => {
  const ee = new EventEmitter();
  let index = 0;
  setInterval(() => ee.emit('data', index++, index), 1000);
  for await (const args of ee.toIterator('data')) {
    console.log({ args });
  }
})();
The structure of 'wrappers' field used to look like this:
Map {
  'eventName' => Map { [Function: origin] => [Function: wrapper] },
  'otherEvent' => Map { [Function: otherOrigin] => [Function: otherWrapper] },
  ...
}

But it made the work harder, especially in the 'listeners' method:
listeners(eventName) {
  const { events, wrappers } = this;
  const listeners = events.get(eventName);
  const callbacks = wrappers.get(eventName);
  const reversed = reverseMap(callbacks);
  if (!listeners) return [];
  const result = [];
  for (const listener of listeners) {
    const isWrapped = reversed.has(listener);
    const output = isWrapped ? reversed.get(listener) : listener;
    result.push(output);
  }
  return result;
}
We needed the 'reverseMap' function to get all the listeners. Here you can see that we are iterating over all listeners from events, which means we just need to determine if the listener is wrapped.

So I propose to make mixins to all wrapped functions with an 'origin' field that references the original listener. I really don't like mixins, but since we work with wrapped functions only inside the program abstraction and don't expose them outside, I think this is a normal approach that will simplify the code and make it more efficient.

And now 'wrappers' field looks like:
Map {
  [Function: origin] => [Function: wrapper],
  [Function: otherOrigin] => [FUnction: otherWrapper],
  ...
}
(async () => {
  const ee = new EventEmitter();
  const signal = AbortSignal.timeout(1000);
  let index = 0;
  const timer = setInterval(() => ee.emit('data', index++), 300);
  try {
    for await (const data of ee.toIterator('data', { signal })) {
      console.log({ data });
    }
  } catch (error) {
    console.log(error.message);
  }
  clearInterval(timer);
})();
lib/events.js Outdated Show resolved Hide resolved
lib/events.js Show resolved Hide resolved
lib/events.js Outdated Show resolved Hide resolved
this.off(name, dispose);
return void fn(...args);
once(eventName, listener) {
const wrapper = (...args) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dispose

lib/events.js Outdated Show resolved Hide resolved
lib/events.js Outdated Show resolved Hide resolved
const { signal = null } = options;
return new Promise((resolve, reject) => {
if (!signal) {
return void this.once(eventName, (...args) => void resolve(args));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need use cases because we can't imagine all developer experience without textual representation

Comment on lines +82 to +93
let onAbort = null;
const onSuccess = (...args) => {
signal.removeEventListener('abort', onAbort);
resolve(args);
};
onAbort = () => {
this.off(eventName, onSuccess);
const message = 'The operatopn was aborted';
reject(new Error(message, { cause: signal.reason }));
};
this.once(eventName, onSuccess);
signal.addEventListener('abort', onAbort);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let onAbort = null;
const onSuccess = (...args) => {
signal.removeEventListener('abort', onAbort);
resolve(args);
};
onAbort = () => {
this.off(eventName, onSuccess);
const message = 'The operatopn was aborted';
reject(new Error(message, { cause: signal.reason }));
};
this.once(eventName, onSuccess);
signal.addEventListener('abort', onAbort);
const handlers = {
success: (...args) => {
signal.removeEventListener('abort', handlers.abort);
resolve(args);
},
abort: () => {
this.off(eventName, handlers.success);
const message = 'The operation was aborted';
reject(new Error(message, { cause: signal.reason }));
},
};
this.once(eventName, handlers.success);
signal.addEventListener('abort', handlers.abort);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need research

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants