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

events: support EventTarget in once #27977

Closed

Conversation

benjamingr
Copy link
Member

Add support for EventTarget in the once static method, hopefully this makes it a bit more universal.

Ping @mcollina please take a look.

(I can also revert the if the error change is to big)

Add support for EventTarget in the `once` static method.
@benjamingr benjamingr requested a review from mcollina May 30, 2019 14:19
@ZYSzys ZYSzys added the events Issues and PRs related to the events subsystem / EventEmitter. label May 30, 2019
test/parallel/test-events-once.js Outdated Show resolved Hide resolved
test/parallel/test-events-once.js Outdated Show resolved Hide resolved
Co-Authored-By: Anna Henningsen <github@addaleax.net>
@mcollina
Copy link
Member

Would you mind adding some docs?

doc/api/events.md Outdated Show resolved Hide resolved
doc/api/events.md Outdated Show resolved Hide resolved
doc/api/events.md Outdated Show resolved Hide resolved
Copy link
Member

@apapirovski apapirovski left a comment

Choose a reason for hiding this comment

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

I think this currently doesn't quite do the right thing? Left some notes.

@@ -509,6 +509,17 @@ function once(emitter, name) {
emitter.once('error', errorListener);
}

emitter.once(name, eventListener);
if (typeof emitter.once === 'function' &&
Copy link
Member

Choose a reason for hiding this comment

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

I could be going totally crazy but I feel like this function is incorrect and the test only passes because it runs through the EventEmitter path? I think you need something more like this?

function once(emitter, name) {
  return new Promise((resolve, reject) => {
    if (typeof emitter.once === 'function' &&
        typeof emitter.removeListener === 'function') {
      const eventListener = (...args) => {
        if (errorListener !== undefined) {
          emitter.removeListener('error', errorListener);
        }
        resolve(args);
      };
      let errorListener;

      // Adding an error listener is not optional because
      // if an error is thrown on an event emitter we cannot
      // guarantee that the actual event we are waiting will
      // be fired. The result could be a silent way to create
      // memory or file descriptor leaks, which is something
      // we should avoid.
      if (name !== 'error') {
        errorListener = (err) => {
          emitter.removeListener(name, eventListener);
          reject(err);
        };

        emitter.once('error', errorListener);
      }

      emitter.once(name, eventListener);
      return;
    }

    if (typeof emitter.addEventListener === 'function') {
      // EventTarget does not have `error` event semantics like Node
      // EventEmitters, we do not listen to `error` events here.
      emitter.addEventListener(name, resolve, { once: true });
      return;
    }

    throw new ERR_INVALID_ARG_TYPE('emitter', 'EventEmitter', emitter);
  });
}

Copy link
Member Author

Choose a reason for hiding this comment

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

I am not sure what the difference here - but this can be a brainfart on my behalf

Copy link
Member

Choose a reason for hiding this comment

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

Your original PR still binds the error listener (and unnecessarily creates the eventListener function). It also creates a nested Promise that it then resolves so the original never resolves. I could also be missing something... but when I ran the code, it didn't seem to do what you're expecting.

Copy link
Member Author

Choose a reason for hiding this comment

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

@apapirovski ah wow you're right!

this.on(name, listener);
}
});
};
Copy link
Member

@apapirovski apapirovski May 31, 2019

Choose a reason for hiding this comment

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

Add emitter.removeListener = undefined; to hit the right code path in this test?

given event.

Note: This method is intentionally generic and works with web platform
[EventTarget](WHATWG-EventTarget) instances. `EventTarget`s have no special
`'error'` event semantics and do not listen to the `'error'` event.
Copy link
Member

Choose a reason for hiding this comment

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

Worth mentioning the different argument semantics? It resolves with the Event instance rather than an array of arguments.

Copy link
Member Author

Choose a reason for hiding this comment

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

@apapirovski I was not sure about what to do here - what behavior would you think is less surprising?

Copy link
Member

Choose a reason for hiding this comment

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

More saying we should make clear the distinction in arguments that the callback receives when you use this with EventTarget.

benjamingr and others added 4 commits May 31, 2019 13:10
Co-Authored-By: Joyee Cheung <joyeec9h3@gmail.com>
Co-Authored-By: Joyee Cheung <joyeec9h3@gmail.com>
…gr/io.js into event-emitter-once-event-target
return emitter.once(name, eventListener);
}
if (typeof emitter.addEventListener === 'function') {
return new Promise((resolve) => {
Copy link
Member

Choose a reason for hiding this comment

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

Extra Promise not needed here?

process.nextTick(() => {
emitter.emit('myevent', 42);
});
const [value] = await once(emitter, 'myevent');
Copy link
Member

Choose a reason for hiding this comment

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

Also check that there are no interesting error semantics?

@benjamingr
Copy link
Member Author

Superceded by #29498 (review) :]

@benjamingr benjamingr closed this Sep 11, 2019
@benjamingr benjamingr deleted the event-emitter-once-event-target branch September 11, 2019 01:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
events Issues and PRs related to the events subsystem / EventEmitter.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants