Skip to content

Commit 2e5c01f

Browse files
authored
[feature] Make addEventListener() support the once option (#1754)
1 parent 97ddfce commit 2e5c01f

File tree

3 files changed

+65
-18
lines changed

3 files changed

+65
-18
lines changed

doc/ws.md

+7-3
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
- [Event: 'pong'](#event-pong)
2727
- [Event: 'unexpected-response'](#event-unexpected-response)
2828
- [Event: 'upgrade'](#event-upgrade)
29-
- [websocket.addEventListener(type, listener)](#websocketaddeventlistenertype-listener)
29+
- [websocket.addEventListener(type, listener[, options])](#websocketaddeventlistenertype-listener-options)
3030
- [websocket.binaryType](#websocketbinarytype)
3131
- [websocket.bufferedAmount](#websocketbufferedamount)
3232
- [websocket.close([code[, reason]])](#websocketclosecode-reason)
@@ -89,7 +89,7 @@ is provided with a single argument then that is:
8989
- `secure` {Boolean} `true` if `req.connection.authorized` or
9090
`req.connection.encrypted` is set.
9191

92-
The return value (Boolean) of the function determines whether or not to accept
92+
The return value (`Boolean`) of the function determines whether or not to accept
9393
the handshake.
9494

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

339-
### websocket.addEventListener(type, listener)
339+
### websocket.addEventListener(type, listener[, options])
340340

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

344348
Register an event listener emulating the `EventTarget` interface.
345349

lib/event-target.js

+22-15
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,16 @@ const EventTarget = {
109109
/**
110110
* Register an event listener.
111111
*
112-
* @param {String} method A string representing the event type to listen for
112+
* @param {String} type A string representing the event type to listen for
113113
* @param {Function} listener The listener to add
114+
* @param {Object} options An options object specifies characteristics about
115+
* the event listener
116+
* @param {Boolean} options.once A `Boolean`` indicating that the listener
117+
* should be invoked at most once after being added. If `true`, the
118+
* listener would be automatically removed when invoked.
114119
* @public
115120
*/
116-
addEventListener(method, listener) {
121+
addEventListener(type, listener, options) {
117122
if (typeof listener !== 'function') return;
118123

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

135-
if (method === 'message') {
140+
const method = options && options.once ? 'once' : 'on';
141+
142+
if (type === 'message') {
136143
onMessage._listener = listener;
137-
this.on(method, onMessage);
138-
} else if (method === 'close') {
144+
this[method](type, onMessage);
145+
} else if (type === 'close') {
139146
onClose._listener = listener;
140-
this.on(method, onClose);
141-
} else if (method === 'error') {
147+
this[method](type, onClose);
148+
} else if (type === 'error') {
142149
onError._listener = listener;
143-
this.on(method, onError);
144-
} else if (method === 'open') {
150+
this[method](type, onError);
151+
} else if (type === 'open') {
145152
onOpen._listener = listener;
146-
this.on(method, onOpen);
153+
this[method](type, onOpen);
147154
} else {
148-
this.on(method, listener);
155+
this[method](type, listener);
149156
}
150157
},
151158

152159
/**
153160
* Remove an event listener.
154161
*
155-
* @param {String} method A string representing the event type to remove
162+
* @param {String} type A string representing the event type to remove
156163
* @param {Function} listener The listener to remove
157164
* @public
158165
*/
159-
removeEventListener(method, listener) {
160-
const listeners = this.listeners(method);
166+
removeEventListener(type, listener) {
167+
const listeners = this.listeners(type);
161168

162169
for (let i = 0; i < listeners.length; i++) {
163170
if (listeners[i] === listener || listeners[i]._listener === listener) {
164-
this.removeListener(method, listeners[i]);
171+
this.removeListener(type, listeners[i]);
165172
}
166173
}
167174
}

test/websocket.test.js

+36
Original file line numberDiff line numberDiff line change
@@ -1809,6 +1809,22 @@ describe('WebSocket', () => {
18091809
assert.strictEqual(ws.listeners('bar').length, 0);
18101810
});
18111811

1812+
it('allows to add one time listeners with `addEventListener`', (done) => {
1813+
const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() });
1814+
1815+
ws.addEventListener(
1816+
'foo',
1817+
() => {
1818+
assert.strictEqual(ws.listenerCount('foo'), 0);
1819+
done();
1820+
},
1821+
{ once: true }
1822+
);
1823+
1824+
assert.strictEqual(ws.listenerCount('foo'), 1);
1825+
ws.emit('foo');
1826+
});
1827+
18121828
it('supports the `removeEventListener` method', () => {
18131829
const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() });
18141830

@@ -1831,6 +1847,26 @@ describe('WebSocket', () => {
18311847
assert.strictEqual(ws.listenerCount('message'), 0);
18321848
assert.strictEqual(ws.listenerCount('open'), 0);
18331849
assert.strictEqual(ws.listenerCount('foo'), 0);
1850+
1851+
ws.addEventListener('message', NOOP, { once: true });
1852+
ws.addEventListener('open', NOOP, { once: true });
1853+
ws.addEventListener('foo', NOOP, { once: true });
1854+
1855+
assert.strictEqual(ws.listeners('message')[0]._listener, NOOP);
1856+
assert.strictEqual(ws.listeners('open')[0]._listener, NOOP);
1857+
assert.strictEqual(ws.listeners('foo')[0], NOOP);
1858+
1859+
ws.removeEventListener('message', () => {});
1860+
1861+
assert.strictEqual(ws.listeners('message')[0]._listener, NOOP);
1862+
1863+
ws.removeEventListener('message', NOOP);
1864+
ws.removeEventListener('open', NOOP);
1865+
ws.removeEventListener('foo', NOOP);
1866+
1867+
assert.strictEqual(ws.listenerCount('message'), 0);
1868+
assert.strictEqual(ws.listenerCount('open'), 0);
1869+
assert.strictEqual(ws.listenerCount('foo'), 0);
18341870
});
18351871

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

0 commit comments

Comments
 (0)