Skip to content

Commit

Permalink
[feature] Make addEventListener() support the once option (#1754)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sceat committed May 7, 2020
1 parent 97ddfce commit 2e5c01f
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 18 deletions.
10 changes: 7 additions & 3 deletions doc/ws.md
Expand Up @@ -26,7 +26,7 @@
- [Event: 'pong'](#event-pong)
- [Event: 'unexpected-response'](#event-unexpected-response)
- [Event: 'upgrade'](#event-upgrade)
- [websocket.addEventListener(type, listener)](#websocketaddeventlistenertype-listener)
- [websocket.addEventListener(type, listener[, options])](#websocketaddeventlistenertype-listener-options)
- [websocket.binaryType](#websocketbinarytype)
- [websocket.bufferedAmount](#websocketbufferedamount)
- [websocket.close([code[, reason]])](#websocketclosecode-reason)
Expand Down Expand Up @@ -89,7 +89,7 @@ is provided with a single argument then that is:
- `secure` {Boolean} `true` if `req.connection.authorized` or
`req.connection.encrypted` is set.

The return value (Boolean) of the function determines whether or not to accept
The return value (`Boolean`) of the function determines whether or not to accept
the handshake.

if `verifyClient` is provided with two arguments then those are:
Expand Down Expand Up @@ -336,10 +336,14 @@ Emitted when response headers are received from the server as part of the
handshake. This allows you to read headers from the server, for example
'set-cookie' headers.

### websocket.addEventListener(type, listener)
### websocket.addEventListener(type, listener[, options])

- `type` {String} A string representing the event type to listen for.
- `listener` {Function} The listener to add.
- `options` {Object}
- `once` {Boolean} A `Boolean` indicating that the listener should be invoked
at most once after being added. If `true`, the listener would be
automatically removed when invoked.

Register an event listener emulating the `EventTarget` interface.

Expand Down
37 changes: 22 additions & 15 deletions lib/event-target.js
Expand Up @@ -109,11 +109,16 @@ const EventTarget = {
/**
* Register an event listener.
*
* @param {String} method A string representing the event type to listen for
* @param {String} type A string representing the event type to listen for
* @param {Function} listener The listener to add
* @param {Object} options An options object specifies characteristics about
* the event listener
* @param {Boolean} options.once A `Boolean`` indicating that the listener
* should be invoked at most once after being added. If `true`, the
* listener would be automatically removed when invoked.
* @public
*/
addEventListener(method, listener) {
addEventListener(type, listener, options) {
if (typeof listener !== 'function') return;

function onMessage(data) {
Expand All @@ -132,36 +137,38 @@ const EventTarget = {
listener.call(this, new OpenEvent(this));
}

if (method === 'message') {
const method = options && options.once ? 'once' : 'on';

if (type === 'message') {
onMessage._listener = listener;
this.on(method, onMessage);
} else if (method === 'close') {
this[method](type, onMessage);
} else if (type === 'close') {
onClose._listener = listener;
this.on(method, onClose);
} else if (method === 'error') {
this[method](type, onClose);
} else if (type === 'error') {
onError._listener = listener;
this.on(method, onError);
} else if (method === 'open') {
this[method](type, onError);
} else if (type === 'open') {
onOpen._listener = listener;
this.on(method, onOpen);
this[method](type, onOpen);
} else {
this.on(method, listener);
this[method](type, listener);
}
},

/**
* Remove an event listener.
*
* @param {String} method A string representing the event type to remove
* @param {String} type A string representing the event type to remove
* @param {Function} listener The listener to remove
* @public
*/
removeEventListener(method, listener) {
const listeners = this.listeners(method);
removeEventListener(type, listener) {
const listeners = this.listeners(type);

for (let i = 0; i < listeners.length; i++) {
if (listeners[i] === listener || listeners[i]._listener === listener) {
this.removeListener(method, listeners[i]);
this.removeListener(type, listeners[i]);
}
}
}
Expand Down
36 changes: 36 additions & 0 deletions test/websocket.test.js
Expand Up @@ -1809,6 +1809,22 @@ describe('WebSocket', () => {
assert.strictEqual(ws.listeners('bar').length, 0);
});

it('allows to add one time listeners with `addEventListener`', (done) => {
const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() });

ws.addEventListener(
'foo',
() => {
assert.strictEqual(ws.listenerCount('foo'), 0);
done();
},
{ once: true }
);

assert.strictEqual(ws.listenerCount('foo'), 1);
ws.emit('foo');
});

it('supports the `removeEventListener` method', () => {
const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() });

Expand All @@ -1831,6 +1847,26 @@ describe('WebSocket', () => {
assert.strictEqual(ws.listenerCount('message'), 0);
assert.strictEqual(ws.listenerCount('open'), 0);
assert.strictEqual(ws.listenerCount('foo'), 0);

ws.addEventListener('message', NOOP, { once: true });
ws.addEventListener('open', NOOP, { once: true });
ws.addEventListener('foo', NOOP, { once: true });

assert.strictEqual(ws.listeners('message')[0]._listener, NOOP);
assert.strictEqual(ws.listeners('open')[0]._listener, NOOP);
assert.strictEqual(ws.listeners('foo')[0], NOOP);

ws.removeEventListener('message', () => {});

assert.strictEqual(ws.listeners('message')[0]._listener, NOOP);

ws.removeEventListener('message', NOOP);
ws.removeEventListener('open', NOOP);
ws.removeEventListener('foo', NOOP);

assert.strictEqual(ws.listenerCount('message'), 0);
assert.strictEqual(ws.listenerCount('open'), 0);
assert.strictEqual(ws.listenerCount('foo'), 0);
});

it('wraps text data in a `MessageEvent`', (done) => {
Expand Down

0 comments on commit 2e5c01f

Please sign in to comment.