diff options
-rw-r--r-- | src/bun.js/bindings/headers-cpp.h | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.h | 2 | ||||
-rw-r--r-- | src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp | 189 | ||||
-rw-r--r-- | src/bun.js/builtins/js/NodeEvents.js | 191 | ||||
-rw-r--r-- | test/js/node/events/node-events.node.test.ts | 261 |
5 files changed, 276 insertions, 369 deletions
diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h index 06a42eb57..5eac6785d 100644 --- a/src/bun.js/bindings/headers-cpp.h +++ b/src/bun.js/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1680036484 +//-- AUTOGENERATED FILE -- 1680635796 // clang-format off #pragma once diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index a53938787..5b0e9527c 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format off -//-- AUTOGENERATED FILE -- 1680036484 +//-- AUTOGENERATED FILE -- 1680635796 #pragma once #include <stddef.h> diff --git a/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp b/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp index 6ae9863cb..c6f2b3895 100644 --- a/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp +++ b/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp @@ -51,42 +51,43 @@ namespace WebCore { const JSC::ConstructAbility s_nodeEventsOnAsyncIteratorCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_nodeEventsOnAsyncIteratorCodeConstructorKind = JSC::ConstructorKind::None; const JSC::ImplementationVisibility s_nodeEventsOnAsyncIteratorCodeImplementationVisibility = JSC::ImplementationVisibility::Public; -const int s_nodeEventsOnAsyncIteratorCodeLength = 4024; +const int s_nodeEventsOnAsyncIteratorCodeLength = 4473; static const JSC::Intrinsic s_nodeEventsOnAsyncIteratorCodeIntrinsic = JSC::NoIntrinsic; const char* const s_nodeEventsOnAsyncIteratorCode = "(function (emitter, event, options) {\n" \ " \"use strict\";\n" \ "\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ + " var { AbortSignal, Number, Error } = globalThis;\n" \ "\n" \ - " //\n" \ - " var { Object, Number, console, Symbol } = globalThis;\n" \ - " //\n" \ + " var AbortError = class AbortError extends Error {\n" \ + " constructor(message = \"The operation was aborted\", options = void 0) {\n" \ + " if (options !== void 0 && typeof options !== \"object\") {\n" \ + " throw new Error(`Invalid AbortError options:\\n" \ + "\\n" \ + "${JSON.stringify(options, null, 2)}`);\n" \ + " }\n" \ + " super(message, options);\n" \ + " this.code = \"ABORT_ERR\";\n" \ + " this.name = \"AbortError\";\n" \ + " }\n" \ + " };\n" \ "\n" \ - " function isUndefinedOrNull(value) {\n" \ - " return value === undefined || value === null;\n" \ - " }\n" \ + " if (@isUndefinedOrNull(emitter)) @throwTypeError(\"emitter is required\");\n" \ + " //\n" \ + " if (!(typeof emitter === \"object\" && @isCallable(emitter.emit) && @isCallable(emitter.on)))\n" \ + " @throwTypeError(\"emitter must be an EventEmitter\");\n" \ "\n" \ - " if (isUndefinedOrNull(options)) options = {};\n" \ + " if (@isUndefinedOrNull(options)) options = {};\n" \ "\n" \ " //\n" \ " var signal = options.signal;\n" \ - " //\n" \ - " //\n" \ + " if (!@isUndefinedOrNull(signal) && !(signal instanceof AbortSignal))\n" \ + " @throwTypeError(\"options.signal must be an AbortSignal\");\n" \ "\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ + " if (signal?.aborted) {\n" \ + " //\n" \ + " throw new AbortError(@undefined, { cause: signal?.reason });\n" \ + " }\n" \ "\n" \ " var highWatermark = options.highWatermark ?? Number.MAX_SAFE_INTEGER;\n" \ " if (highWatermark < 1) \n" \ @@ -105,9 +106,9 @@ const char* const s_nodeEventsOnAsyncIteratorCode = " var size = 0;\n" \ " var listeners = [];\n" \ "\n" \ - " //\n" \ - " //\n" \ - " //\n" \ + " function abortListener() {\n" \ + " errorHandler(new AbortError(@undefined, { cause: signal?.reason }));\n" \ + " }\n" \ "\n" \ " function eventHandler(value) {\n" \ " if (unconsumedPromises.isEmpty()) {\n" \ @@ -117,22 +118,22 @@ const char* const s_nodeEventsOnAsyncIteratorCode = " emitter.pause();\n" \ " }\n" \ " unconsumedEvents.push(value);\n" \ - " } else unconsumedPromises.shift().@resolve.@call(undefined, [value]);\n" \ + " } else unconsumedPromises.shift().@resolve.@call(@undefined, [value]);\n" \ " }\n" \ "\n" \ " function closeHandler() {\n" \ " removeAllListeners(listeners);\n" \ " finished = true;\n" \ " while (!unconsumedPromises.isEmpty()) {\n" \ - " unconsumedPromises.shift().@resolve.@call(undefined, [undefined]);\n" \ + " const promise = unconsumedPromises.shift();\n" \ + " promise.@resolve.@call(@undefined, [@undefined]);\n" \ " }\n" \ - " \n" \ - " return @createFulfilledPromise([undefined]);\n" \ + " return @createFulfilledPromise([@undefined]);\n" \ " }\n" \ "\n" \ " function errorHandler(err) {\n" \ " if (unconsumedPromises.isEmpty()) error = err;\n" \ - " else unconsumedPromises.shift().@reject.@call(undefined, err);\n" \ + " else unconsumedPromises.shift().@reject.@call(@undefined, err);\n" \ " \n" \ " closeHandler();\n" \ " }\n" \ @@ -140,37 +141,30 @@ const char* const s_nodeEventsOnAsyncIteratorCode = " function addEventListener(emitter, event, handler) {\n" \ " emitter.on(event, handler);\n" \ " listeners.push([emitter, event, handler]);\n" \ - " //\n" \ " }\n" \ " \n" \ " function removeAllListeners() {\n" \ - " var heldEmitter;\n" \ " while (listeners.length > 0) {\n" \ - " //\n" \ " var entry = listeners.pop();\n" \ " var [emitter, event, handler] = entry;\n" \ - " if (event === \"foo\") heldEmitter = emitter;\n" \ - " console.log(emitter, event, handler);\n" \ " emitter.off(event, handler);\n" \ " }\n" \ - " console.log(heldEmitter.listenerCount(\"foo\"));\n" \ " }\n" \ "\n" \ - " var iterator = async function* NodeEventsOnAsyncIterator() {\n" \ - " //\n" \ - " while (!finished) {\n" \ - " if (size) {\n" \ - " var values = [];\n" \ - " while (size) {\n" \ - " values.push(unconsumedEvents.shift());\n" \ - " size--;\n" \ - " if (paused && size < lowWatermark) {\n" \ - " emitter.resume();\n" \ - " paused = false;\n" \ - " break;\n" \ - " }\n" \ + " var createIterator = async function* NodeEventsOnAsyncIterator() {\n" \ + " //\n" \ + " try {\n" \ + " while (true) {\n" \ + " //\n" \ + " while (size) {\n" \ + " const value = unconsumedEvents.shift();\n" \ + " size--;\n" \ + " if (paused && size < lowWatermark) {\n" \ + " emitter.resume();\n" \ + " paused = false;\n" \ + " break;\n" \ " }\n" \ - " yield @createFulfilledPromise(values);\n" \ + " yield @createFulfilledPromise([value]);\n" \ " }\n" \ "\n" \ " //\n" \ @@ -181,67 +175,19 @@ const char* const s_nodeEventsOnAsyncIteratorCode = " }\n" \ "\n" \ " //\n" \ - " if (finished) {\n" \ - " console.log(\"FINISHED\")\n" \ - " yield closeHandler();\n" \ - " break;\n" \ - " };\n" \ + " if (finished) break;\n" \ "\n" \ " //\n" \ " var nextEventPromiseCapability = @newPromiseCapability(@Promise);\n" \ " unconsumedPromises.push(nextEventPromiseCapability);\n" \ " yield nextEventPromiseCapability.@promise;\n" \ " }\n" \ + " } finally {\n" \ + " closeHandler();\n" \ + " }\n" \ " };\n" \ "\n" \ " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - " //\n" \ - "\n" \ - " //\n" \ " addEventListener(emitter, event, eventHandler);\n" \ " if (event !== \"error\" && typeof emitter.on === \"function\") {\n" \ " addEventListener(emitter, \"error\", errorHandler);\n" \ @@ -253,23 +199,30 @@ const char* const s_nodeEventsOnAsyncIteratorCode = " }\n" \ " }\n" \ "\n" \ - " //\n" \ - " //\n" \ + " if (signal)\n" \ + " signal.once(\"abort\", abortListener);\n" \ "\n" \ - " return {\n" \ - " throw: (err) => {\n" \ - " if (err === undefined || err === null || !(err instanceof Error)) {\n" \ - " @throwTypeError(\"The argument must be an instance of Error\");\n" \ - " }\n" \ - " errorHandler(err);\n" \ + " var iterator = createIterator();\n" \ + "\n" \ + " @Object.defineProperties(iterator, {\n" \ + " return: {\n" \ + " value: function() {\n" \ + " return closeHandler();\n" \ + " },\n" \ " },\n" \ - " return: () => {\n" \ - " console.log(\"we're here\");\n" \ - " return closeHandler();\n" \ + " throw: {\n" \ + " value: function(err) {\n" \ + " if (!err || !(err instanceof Error)) {\n" \ + " throw new TypeError(\"EventEmitter.AsyncIterator must be called with an error\");\n" \ + " }\n" \ + " errorHandler(err);\n" \ + " },\n" \ " },\n" \ - " next: () => iterator.next(),\n" \ - " [Symbol.asyncIterator]: iterator,\n" \ - " };\n" \ + " [Symbol.asyncIterator]: {\n" \ + " value: function() { return this; }\n" \ + " },\n" \ + " });\n" \ + " return iterator;\n" \ "})\n" \ ; diff --git a/src/bun.js/builtins/js/NodeEvents.js b/src/bun.js/builtins/js/NodeEvents.js index 02e944fde..6c80f81c8 100644 --- a/src/bun.js/builtins/js/NodeEvents.js +++ b/src/bun.js/builtins/js/NodeEvents.js @@ -26,36 +26,35 @@ function onAsyncIterator(emitter, event, options) { "use strict"; - // var AbortError = class AbortError extends Error { - // constructor(message = "The operation was aborted", options = void 0) { - // if (options !== void 0 && typeof options !== "object") { - // throw new Error(`Invalid AbortError options:\n\n${JSON.stringify(options, null, 2)}`); - // } - // super(message, options); - // this.code = "ABORT_ERR"; - // this.name = "AbortError"; - // } - // }; - - // var { AbortSignal, Object, Number, console } = globalThis; - var { Object, Number, console, Symbol } = globalThis; - // console.log("AbortSignal", AbortSignal); - - function isUndefinedOrNull(value) { - return value === undefined || value === null; - } + var { AbortSignal, Number, Error } = globalThis; + + var AbortError = class AbortError extends Error { + constructor(message = "The operation was aborted", options = void 0) { + if (options !== void 0 && typeof options !== "object") { + throw new Error(`Invalid AbortError options:\n\n${JSON.stringify(options, null, 2)}`); + } + super(message, options); + this.code = "ABORT_ERR"; + this.name = "AbortError"; + } + }; + + if (@isUndefinedOrNull(emitter)) @throwTypeError("emitter is required"); + // TODO: Do a more accurate check + if (!(typeof emitter === "object" && @isCallable(emitter.emit) && @isCallable(emitter.on))) + @throwTypeError("emitter must be an EventEmitter"); - if (isUndefinedOrNull(options)) options = {}; + if (@isUndefinedOrNull(options)) options = {}; // Parameters validation var signal = options.signal; - // if (!isUndefinedOrNull(signal) && !(signal instanceof AbortSignal)) - // @throwTypeError("options.signal must be an AbortSignal"); + if (!@isUndefinedOrNull(signal) && !(signal instanceof AbortSignal)) + @throwTypeError("options.signal must be an AbortSignal"); - // if (signal?.aborted) { - // // TODO: Make this a builtin - // throw new AbortError(undefined, { cause: signal?.reason }); - // } + if (signal?.aborted) { + // TODO: Make this a builtin + throw new AbortError(@undefined, { cause: signal?.reason }); + } var highWatermark = options.highWatermark ?? Number.MAX_SAFE_INTEGER; if (highWatermark < 1) @@ -74,9 +73,9 @@ function onAsyncIterator(emitter, event, options) { var size = 0; var listeners = []; - // function abortListener() { - // errorHandler(new AbortError(undefined, { cause: signal?.reason })); - // } + function abortListener() { + errorHandler(new AbortError(@undefined, { cause: signal?.reason })); + } function eventHandler(value) { if (unconsumedPromises.isEmpty()) { @@ -86,22 +85,22 @@ function onAsyncIterator(emitter, event, options) { emitter.pause(); } unconsumedEvents.push(value); - } else unconsumedPromises.shift().@resolve.@call(undefined, [value]); + } else unconsumedPromises.shift().@resolve.@call(@undefined, [value]); } function closeHandler() { removeAllListeners(listeners); finished = true; while (!unconsumedPromises.isEmpty()) { - unconsumedPromises.shift().@resolve.@call(undefined, [undefined]); + const promise = unconsumedPromises.shift(); + promise.@resolve.@call(@undefined, [@undefined]); } - - return @createFulfilledPromise([undefined]); + return @createFulfilledPromise([@undefined]); } function errorHandler(err) { if (unconsumedPromises.isEmpty()) error = err; - else unconsumedPromises.shift().@reject.@call(undefined, err); + else unconsumedPromises.shift().@reject.@call(@undefined, err); closeHandler(); } @@ -109,40 +108,33 @@ function onAsyncIterator(emitter, event, options) { function addEventListener(emitter, event, handler) { emitter.on(event, handler); listeners.push([emitter, event, handler]); - // @arrayPush(listeners, [emitter, event, handler]); } function removeAllListeners() { - var heldEmitter; while (listeners.length > 0) { - // var [emitter, event, handler] = @arrayPop(listeners); var entry = listeners.pop(); var [emitter, event, handler] = entry; - if (event === "foo") heldEmitter = emitter; - console.log(emitter, event, handler); emitter.off(event, handler); } - console.log(heldEmitter.listenerCount("foo")); } - var iterator = async function* NodeEventsOnAsyncIterator() { - // First, we consume all unread events - while (!finished) { - if (size) { - var values = []; - while (size) { - values.push(unconsumedEvents.shift()); - size--; - if (paused && size < lowWatermark) { - emitter.resume(); - paused = false; - break; - } + var createIterator = async function* NodeEventsOnAsyncIterator() { + // First, we consume all unread events + try { + while (true) { + // Go through queued events + while (size) { + const value = unconsumedEvents.shift(); + size--; + if (paused && size < lowWatermark) { + emitter.resume(); + paused = false; + break; } - yield @createFulfilledPromise(values); + yield @createFulfilledPromise([value]); } - // Then we error, if an error happened + // Check if error happened before yielding anything // This happens one time if at all, because after 'error' // we stop listening if (error) { @@ -150,66 +142,18 @@ function onAsyncIterator(emitter, event, options) { } // If the iterator is finished, break - if (finished) { - console.log("FINISHED") - yield closeHandler(); - break; - }; + if (finished) break; // Wait until an event happens var nextEventPromiseCapability = @newPromiseCapability(@Promise); unconsumedPromises.push(nextEventPromiseCapability); yield nextEventPromiseCapability.@promise; } + } finally { + closeHandler(); + } }; - // // TODO: Use builtin - // Object.defineProperties(iterator, { - // "throw": { - // value: (err) => { - // // TODO: Use Error builtin? - // if (err === undefined || err === null || !(err instanceof Error)) { - // @throwTypeError("The argument must be an instance of Error"); - // } - // errorHandler(err); - // }, - // }, - // "return": { - // value: () => { - // return closeHandler(); - // } - // }, - // [Symbol.asyncIterator]: { - // value: () => iterator, - // }, - // // [kWatermarkData]: { - // // /** - // // * The current queue size - // // */ - // // get size() { - // // return size; - // // }, - // // /** - // // * The low watermark. The emitter is resumed every time size is lower than it - // // */ - // // get low() { - // // return lowWatermark; - // // }, - // // /** - // // * The high watermark. The emitter is paused every time size is higher than it - // // */ - // // get high() { - // // return highWatermark; - // // }, - // // /** - // // * It checks whether the emitter is paused by the watermark controller or not - // // */ - // // get isPaused() { - // // return paused; - // // }, - // // }, - // }); - // Adding event handlers addEventListener(emitter, event, eventHandler); if (event !== "error" && typeof emitter.on === "function") { @@ -222,21 +166,28 @@ function onAsyncIterator(emitter, event, options) { } } - // if (signal) - // signal.once("abort", abortListener); + if (signal) + signal.once("abort", abortListener); - return { - throw: (err) => { - if (err === undefined || err === null || !(err instanceof Error)) { - @throwTypeError("The argument must be an instance of Error"); - } - errorHandler(err); + var iterator = createIterator(); + + @Object.defineProperties(iterator, { + return: { + value: function() { + return closeHandler(); + }, + }, + throw: { + value: function(err) { + if (!err || !(err instanceof Error)) { + throw new TypeError("EventEmitter.AsyncIterator must be called with an error"); + } + errorHandler(err); + }, }, - return: () => { - console.log("we're here"); - return closeHandler(); + [Symbol.asyncIterator]: { + value: function() { return this; } }, - next: () => iterator.next(), - [Symbol.asyncIterator]: iterator, - }; + }); + return iterator; } diff --git a/test/js/node/events/node-events.node.test.ts b/test/js/node/events/node-events.node.test.ts index ac0e163ba..579d7ec5b 100644 --- a/test/js/node/events/node-events.node.test.ts +++ b/test/js/node/events/node-events.node.test.ts @@ -5,26 +5,19 @@ const { expect, assert, describe, it, createCallCheckCtx, createDoneDotAll } = c // const NodeEventTarget = globalThis.EventTarget; describe("node:events.on (EE async iterator)", () => { - it("should return an async iterator", async done => { - let resolveDeferred: () => void; - const deferred = new Promise(resolve => { - resolveDeferred = resolve as () => void; - }); + it("should return an async iterator", async () => { const ee = new EventEmitter(); - process.nextTick(() => { - ee.emit("foo", "bar"); - // 'bar' is a spurious event, we are testing - // that it does not show up in the iterable - ee.emit("bar", 24); - ee.emit("foo", 42); - resolveDeferred(); // Resolve in the next tick - }); - const iterable = on(ee, "foo"); + + ee.emit("foo", "bar"); + // 'bar' is a spurious event, we are testing + // that it does not show up in the iterable + ee.emit("bar", 24); + ee.emit("foo", 42); + const expected = [["bar"], [42]]; for await (const event of iterable) { - console.log(event); const current = expected.shift(); assert.deepStrictEqual(current, event); @@ -34,34 +27,29 @@ describe("node:events.on (EE async iterator)", () => { } } - // This is necessary due to how we try to run emits on next tick - deferred - .then(() => { - assert.strictEqual(ee.listenerCount("foo"), 0); - assert.strictEqual(ee.listenerCount("error"), 0); - }) - .catch(done); + assert.strictEqual(ee.listenerCount("foo"), 0); + assert.strictEqual(ee.listenerCount("error"), 0); }); - // TODO: Fix undefined is not a function - it.skip("should throw an error when the first argument is not an EventEmitter", async () => { - expect(on({} as any, "foo")).toThrow(TypeError); + it("should throw an error when the first argument is not an EventEmitter", () => { + expect(() => on({} as any, "foo")).toThrow(); }); it("should throw an error when an error event is emitted", async () => { const ee = new EventEmitter(); const _err = new Error("kaboom"); - process.nextTick(() => { - ee.emit("error", _err); - }); const iterable = on(ee, "foo"); + + ee.emit("error", _err); + let looped = false; let thrown = false; try { // eslint-disable-next-line no-unused-vars for await (const event of iterable) { + console.log("LOOPED?!"); looped = true; } } catch (err) { @@ -72,133 +60,148 @@ describe("node:events.on (EE async iterator)", () => { assert.strictEqual(looped, false); }); - it("should throw when error emitted", async done => { - let resolveDeferred: () => void; - const deferred = new Promise(resolve => { - resolveDeferred = resolve as () => void; - }); - + it("should throw when error emitted after successful events", async () => { const ee = new EventEmitter(); const _err = new Error("kaboom"); - process.nextTick(() => { - ee.emit("foo", 42); - ee.emit("error", _err); - resolveDeferred(); - }); - const iterable = on(ee, "foo"); - const expected = [[42]]; - const current = [] as (number[] | undefined)[]; - const received = [] as (number[] | undefined)[]; + ee.emit("foo", 42); + ee.emit("error", _err); + + const expected = [[42]] as (number[] | undefined[])[]; + + const current = [] as (number[] | undefined[])[]; + const received = [] as (number[] | undefined[])[]; let thrownErr: any; try { for await (const event of iterable) { - current.push(expected.shift()); + const _expected = expected.shift(); + if (_expected !== undefined) current.push(_expected); received.push(event); } } catch (err) { - console.log(err); thrownErr = err; } - deferred - .then(() => { - assert.deepStrictEqual(current, received); - assert.strictEqual(ee.listenerCount("foo"), 0); - assert.strictEqual(ee.listenerCount("error"), 0); + assert.deepStrictEqual(current, received); + assert.strictEqual(ee.listenerCount("foo"), 0); + assert.strictEqual(ee.listenerCount("error"), 0); - expect(thrownErr).toBeInstanceOf(Error); - assert.strictEqual(thrownErr, _err); - }) - .catch(done); + expect(thrownErr).toBeInstanceOf(Error); + assert.strictEqual(thrownErr, _err); }); - // async function throwInLoop() { - // const ee = new EventEmitter(); - // const _err = new Error("kaboom"); + it("should throw when error thrown from inside loop", async () => { + const ee = new EventEmitter(); + const _err = new Error("kaboom"); - // process.nextTick(() => { - // ee.emit("foo", 42); - // }); + const iterable = on(ee, "foo"); - // try { - // for await (const event of on(ee, "foo")) { - // assert.deepStrictEqual(event, [42]); - // throw _err; - // } - // } catch (err) { - // assert.strictEqual(err, _err); - // } + ee.emit("foo", 42); - // assert.strictEqual(ee.listenerCount("foo"), 0); - // assert.strictEqual(ee.listenerCount("error"), 0); - // } + let looped = false; + let thrown = false; - // async function next() { - // const ee = new EventEmitter(); - // const iterable = on(ee, "foo"); + try { + // eslint-disable-next-line no-unused-vars + for await (const event of iterable) { + assert.deepStrictEqual(event, [42]); + looped = true; + throw _err; + } + } catch (err) { + thrown = true; + assert.strictEqual(err, _err); + } - // process.nextTick(function () { - // ee.emit("foo", "bar"); - // ee.emit("foo", 42); - // iterable.return(); - // }); + assert.strictEqual(thrown, true); + assert.strictEqual(looped, true); + assert.strictEqual(ee.listenerCount("foo"), 0); + assert.strictEqual(ee.listenerCount("error"), 0); + }); - // const results = await Promise.all([iterable.next(), iterable.next(), iterable.next()]); + it("should allow for async iteration via .next()", async done => { + const ee = new EventEmitter(); + const iterable = on(ee, "foo"); - // assert.deepStrictEqual(results, [ - // { - // value: ["bar"], - // done: false, - // }, - // { - // value: [42], - // done: false, - // }, - // { - // value: undefined, - // done: true, - // }, - // ]); + process.nextTick(() => { + ee.emit("foo", "bar"); + ee.emit("foo", 42); + // @ts-ignore + iterable.return(); + }); - // assert.deepStrictEqual(await iterable.next(), { - // value: undefined, - // done: true, - // }); - // } + const results = await Promise.all([iterable.next(), iterable.next(), iterable.next()]); + assert.deepStrictEqual(results, [ + { + value: ["bar"], + done: false, + }, + { + value: [42], + done: false, + }, + { + value: undefined, + done: true, + }, + ]); + + assert.deepStrictEqual(await iterable.next(), { + value: undefined, + done: true, + }); - // async function nextError() { - // const ee = new EventEmitter(); - // const iterable = on(ee, "foo"); - // const _err = new Error("kaboom"); - // process.nextTick(function () { - // ee.emit("error", _err); - // }); - // const results = await Promise.allSettled([iterable.next(), iterable.next(), iterable.next()]); - // assert.deepStrictEqual(results, [ - // { - // status: "rejected", - // reason: _err, - // }, - // { - // status: "fulfilled", - // value: { - // value: undefined, - // done: true, - // }, - // }, - // { - // status: "fulfilled", - // value: { - // value: undefined, - // done: true, - // }, - // }, - // ]); - // assert.strictEqual(ee.listeners("error").length, 0); - // } + done(); + }); + + it("it should fulfill subsequent deferred promises with `undefined` when the emitter emits an error", async done => { + const ee = new EventEmitter(); + const iterable = on(ee, "foo"); + const _err = new Error("kaboom"); + + process.nextTick(function () { + ee.emit("error", _err); + }); + + const results = await Promise.allSettled([iterable.next(), iterable.next(), iterable.next()]); + + assert.deepStrictEqual(results, [ + { + status: "rejected", + reason: _err, + }, + { + status: "fulfilled", + value: { + value: undefined, + done: true, + }, + }, + { + status: "fulfilled", + value: { + value: undefined, + done: true, + }, + }, + ]); + + assert.strictEqual(ee.listeners("error").length, 0); + + done(); + }); + + it("should throw a `TypeError` when calling throw without args", async () => { + const ee = new EventEmitter(); + const iterable = on(ee, "foo"); + + expect(() => { + // @ts-ignore + iterable.throw(); + }).toThrow(TypeError); + }); // async function iterableThrow() { // const ee = new EventEmitter(); |