| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 4b04bb8 commit 1fefb5c
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -829,7 +829,7 @@ class MyClass extends EventEmitter { | |||
| 829 | 829 | } | |
| 830 | 830 | ``` | |
| 831 | 831 | ||
| 832 | - ## `events.once(emitter, name)` | ||
| 832 | + ## `events.once(emitter, name[, options])` | ||
| 833 | 833 | <!-- YAML | |
| 834 | 834 | added: | |
| 835 | 835 | - v11.13.0 | |
@@ -838,6 +838,9 @@ added: | |||
| 838 | 838 | ||
| 839 | 839 | * `emitter` {EventEmitter} | |
| 840 | 840 | * `name` {string} | |
| 841 | + * `options` {Object} | ||
| 842 | + * `signal` {AbortSignal} An {AbortSignal} that may be used to cancel waiting | ||
| 843 | + for the event. | ||
| 841 | 844 | * Returns: {Promise} | |
| 842 | 845 | ||
| 843 | 846 | Creates a `Promise` that is fulfilled when the `EventEmitter` emits the given | |
@@ -896,6 +899,31 @@ ee.emit('error', new Error('boom')); | |||
| 896 | 899 | // Prints: ok boom | |
| 897 | 900 | ``` | |
| 898 | 901 | ||
| 902 | + An {AbortSignal} may be used to cancel waiting for the event early: | ||
| 903 | + | ||
| 904 | + ```js | ||
| 905 | + const { EventEmitter, once } = require('events'); | ||
| 906 | + | ||
| 907 | + const ee = new EventEmitter(); | ||
| 908 | + const ac = new AbortController(); | ||
| 909 | + | ||
| 910 | + async function foo(emitter, event, signal) { | ||
| 911 | + try { | ||
| 912 | + await once(emitter, event, { signal }); | ||
| 913 | + console.log('event emitted!'); | ||
| 914 | + } catch (error) { | ||
| 915 | + if (error.name === 'AbortError') { | ||
| 916 | + console.error('Waiting for the event was canceled!'); | ||
| 917 | + } else { | ||
| 918 | + console.error('There was an error', error.message); | ||
| 919 | + } | ||
| 920 | + } | ||
| 921 | + } | ||
| 922 | + | ||
| 923 | + foo(ee, 'foo', ac.signal); | ||
| 924 | + ac.abort(); // Abort waiting for the event | ||
| 925 | + ``` | ||
| 926 | + | ||
| 899 | 927 | ### Awaiting multiple events emitted on `process.nextTick()` | |
| 900 | 928 | ||
| 901 | 929 | There is an edge case worth noting when using the `events.once()` function | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -46,6 +46,7 @@ const kRejection = SymbolFor('nodejs.rejection'); | |||
| 46 | 46 | let spliceOne; | |
| 47 | 47 | ||
| 48 | 48 | const { | |
| 49 | + hideStackFrames, | ||
| 49 | 50 | kEnhanceStackBeforeInspector, | |
| 50 | 51 | codes | |
| 51 | 52 | } = require('internal/errors'); | |
@@ -59,9 +60,20 @@ const { | |||
| 59 | 60 | inspect | |
| 60 | 61 | } = require('internal/util/inspect'); | |
| 61 | 62 | ||
| 63 | + const { | ||
| 64 | + validateAbortSignal | ||
| 65 | + } = require('internal/validators'); | ||
| 66 | + | ||
| 62 | 67 | const kCapture = Symbol('kCapture'); | |
| 63 | 68 | const kErrorMonitor = Symbol('events.errorMonitor'); | |
| 64 | 69 | ||
| 70 | + let DOMException; | ||
| 71 | + const lazyDOMException = hideStackFrames((message, name) => { | ||
| 72 | + if (DOMException === undefined) | ||
| 73 | + DOMException = internalBinding('messaging').DOMException; | ||
| 74 | + return new DOMException(message, name); | ||
| 75 | + }); | ||
| 76 | + | ||
| 65 | 77 | function EventEmitter(opts) { | |
| 66 | 78 | EventEmitter.init.call(this, opts); | |
| 67 | 79 | } | |
@@ -622,22 +634,61 @@ function unwrapListeners(arr) { | |||
| 622 | 634 | return ret; | |
| 623 | 635 | } | |
| 624 | 636 | ||
| 625 | - function once(emitter, name) { | ||
| 637 | + async function once(emitter, name, options = {}) { | ||
| 638 | + const signal = options ? options.signal : undefined; | ||
| 639 | + validateAbortSignal(signal, 'options.signal'); | ||
| 640 | + if (signal && signal.aborted) | ||
| 641 | + throw lazyDOMException('The operation was aborted', 'AbortError'); | ||
| 626 | 642 | return new Promise((resolve, reject) => { | |
| 627 | 643 | const errorListener = (err) => { | |
| 628 | 644 | emitter.removeListener(name, resolver); | |
| 645 | + if (signal != null) { | ||
| 646 | + eventTargetAgnosticRemoveListener( | ||
| 647 | + signal, | ||
| 648 | + 'abort', | ||
| 649 | + abortListener, | ||
| 650 | + { once: true }); | ||
| 651 | + } | ||
| 629 | 652 | reject(err); | |
| 630 | 653 | }; | |
| 631 | 654 | const resolver = (...args) => { | |
| 632 | 655 | if (typeof emitter.removeListener === 'function') { | |
| 633 | 656 | emitter.removeListener('error', errorListener); | |
| 634 | 657 | } | |
| 658 | + if (signal != null) { | ||
| 659 | + eventTargetAgnosticRemoveListener( | ||
| 660 | + signal, | ||
| 661 | + 'abort', | ||
| 662 | + abortListener, | ||
| 663 | + { once: true }); | ||
| 664 | + } | ||
| 635 | 665 | resolve(args); | |
| 636 | 666 | }; | |
| 637 | 667 | eventTargetAgnosticAddListener(emitter, name, resolver, { once: true }); | |
| 638 | 668 | if (name !== 'error') { | |
| 639 | 669 | addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true }); | |
| 640 | 670 | } | |
| 671 | + function abortListener() { | ||
| 672 | + if (typeof emitter.removeListener === 'function') { | ||
| 673 | + emitter.removeListener(name, resolver); | ||
| 674 | + emitter.removeListener('error', errorListener); | ||
| 675 | + } else { | ||
| 676 | + eventTargetAgnosticRemoveListener( | ||
| 677 | + emitter, | ||
| 678 | + name, | ||
| 679 | + resolver, | ||
| 680 | + { once: true }); | ||
| 681 | + eventTargetAgnosticRemoveListener( | ||
| 682 | + emitter, | ||
| 683 | + 'error', | ||
| 684 | + errorListener, | ||
| 685 | + { once: true }); | ||
| 686 | + } | ||
| 687 | + reject(lazyDOMException('The operation was aborted', 'AbortError')); | ||
| 688 | + } | ||
| 689 | + if (signal != null) { | ||
| 690 | + signal.addEventListener('abort', abortListener, { once: true }); | ||
| 691 | + } | ||
| 641 | 692 | }); | |
| 642 | 693 | } | |
| 643 | 694 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -218,6 +218,15 @@ const validateCallback = hideStackFrames((callback) => { | |||
| 218 | 218 | throw new ERR_INVALID_CALLBACK(callback); | |
| 219 | 219 | }); | |
| 220 | 220 | ||
| 221 | + const validateAbortSignal = hideStackFrames((signal, name) => { | ||
| 222 | + if (signal !== undefined && | ||
| 223 | + (signal === null || | ||
| 224 | + typeof signal !== 'object' || | ||
| 225 | + !('aborted' in signal))) { | ||
| 226 | + throw new ERR_INVALID_ARG_TYPE(name, 'AbortSignal', signal); | ||
| 227 | + } | ||
| 228 | + }); | ||
| 229 | + | ||
| 221 | 230 | module.exports = { | |
| 222 | 231 | isInt32, | |
| 223 | 232 | isUint32, | |
@@ -236,4 +245,5 @@ module.exports = { | |||
| 236 | 245 | validateString, | |
| 237 | 246 | validateUint32, | |
| 238 | 247 | validateCallback, | |
| 248 | + validateAbortSignal, | ||
| 239 | 249 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,9 +1,14 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | - // Flags: --expose-internals | ||
| 2 | + // Flags: --expose-internals --no-warnings --experimental-abortcontroller | ||
| 3 | 3 | ||
| 4 | 4 | const common = require('../common'); | |
| 5 | 5 | const { once, EventEmitter } = require('events'); | |
| 6 | - const { strictEqual, deepStrictEqual, fail } = require('assert'); | ||
| 6 | + const { | ||
| 7 | + strictEqual, | ||
| 8 | + deepStrictEqual, | ||
| 9 | + fail, | ||
| 10 | + rejects, | ||
| 11 | + } = require('assert'); | ||
| 7 | 12 | const { EventTarget, Event } = require('internal/event_target'); | |
| 8 | 13 | ||
| 9 | 14 | async function onceAnEvent() { | |
@@ -114,6 +119,81 @@ async function prioritizesEventEmitter() { | |||
| 114 | 119 | process.nextTick(() => ee.emit('foo')); | |
| 115 | 120 | await once(ee, 'foo'); | |
| 116 | 121 | } | |
| 122 | + | ||
| 123 | + async function abortSignalBefore() { | ||
| 124 | + const ee = new EventEmitter(); | ||
| 125 | + const ac = new AbortController(); | ||
| 126 | + ee.on('error', common.mustNotCall()); | ||
| 127 | + ac.abort(); | ||
| 128 | + | ||
| 129 | + await Promise.all([1, {}, 'hi', null, false].map((signal) => { | ||
| 130 | + return rejects(once(ee, 'foo', { signal }), { | ||
| 131 | + code: 'ERR_INVALID_ARG_TYPE' | ||
| 132 | + }); | ||
| 133 | + })); | ||
| 134 | + | ||
| 135 | + return rejects(once(ee, 'foo', { signal: ac.signal }), { | ||
| 136 | + name: 'AbortError' | ||
| 137 | + }); | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + async function abortSignalAfter() { | ||
| 141 | + const ee = new EventEmitter(); | ||
| 142 | + const ac = new AbortController(); | ||
| 143 | + ee.on('error', common.mustNotCall()); | ||
| 144 | + const r = rejects(once(ee, 'foo', { signal: ac.signal }), { | ||
| 145 | + name: 'AbortError' | ||
| 146 | + }); | ||
| 147 | + process.nextTick(() => ac.abort()); | ||
| 148 | + return r; | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + async function abortSignalAfterEvent() { | ||
| 152 | + const ee = new EventEmitter(); | ||
| 153 | + const ac = new AbortController(); | ||
| 154 | + process.nextTick(() => { | ||
| 155 | + ee.emit('foo'); | ||
| 156 | + ac.abort(); | ||
| 157 | + }); | ||
| 158 | + await once(ee, 'foo', { signal: ac.signal }); | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + async function eventTargetAbortSignalBefore() { | ||
| 162 | + const et = new EventTarget(); | ||
| 163 | + const ac = new AbortController(); | ||
| 164 | + ac.abort(); | ||
| 165 | + | ||
| 166 | + await Promise.all([1, {}, 'hi', null, false].map((signal) => { | ||
| 167 | + return rejects(once(et, 'foo', { signal }), { | ||
| 168 | + code: 'ERR_INVALID_ARG_TYPE' | ||
| 169 | + }); | ||
| 170 | + })); | ||
| 171 | + | ||
| 172 | + return rejects(once(et, 'foo', { signal: ac.signal }), { | ||
| 173 | + name: 'AbortError' | ||
| 174 | + }); | ||
| 175 | + } | ||
| 176 | + | ||
| 177 | + async function eventTargetAbortSignalAfter() { | ||
| 178 | + const et = new EventTarget(); | ||
| 179 | + const ac = new AbortController(); | ||
| 180 | + const r = rejects(once(et, 'foo', { signal: ac.signal }), { | ||
| 181 | + name: 'AbortError' | ||
| 182 | + }); | ||
| 183 | + process.nextTick(() => ac.abort()); | ||
| 184 | + return r; | ||
| 185 | + } | ||
| 186 | + | ||
| 187 | + async function eventTargetAbortSignalAfterEvent() { | ||
| 188 | + const et = new EventTarget(); | ||
| 189 | + const ac = new AbortController(); | ||
| 190 | + process.nextTick(() => { | ||
| 191 | + et.dispatchEvent(new Event('foo')); | ||
| 192 | + ac.abort(); | ||
| 193 | + }); | ||
| 194 | + await once(et, 'foo', { signal: ac.signal }); | ||
| 195 | + } | ||
| 196 | + | ||
| 117 | 197 | Promise.all([ | |
| 118 | 198 | onceAnEvent(), | |
| 119 | 199 | onceAnEventWithTwoArgs(), | |
@@ -123,4 +203,10 @@ Promise.all([ | |||
| 123 | 203 | onceWithEventTarget(), | |
| 124 | 204 | onceWithEventTargetError(), | |
| 125 | 205 | prioritizesEventEmitter(), | |
| 206 | + abortSignalBefore(), | ||
| 207 | + abortSignalAfter(), | ||
| 208 | + abortSignalAfterEvent(), | ||
| 209 | + eventTargetAbortSignalBefore(), | ||
| 210 | + eventTargetAbortSignalAfter(), | ||
| 211 | + eventTargetAbortSignalAfterEvent(), | ||
| 126 | 212 | ]).then(common.mustCall()); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments