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/bindings/webcore/JSEventEmitter.cpp | 52 | ||||
-rw-r--r-- | src/bun.js/builtins/BunBuiltinNames.h | 1 | ||||
-rw-r--r-- | src/bun.js/builtins/README.md | 4 | ||||
-rw-r--r-- | src/bun.js/builtins/WebCoreJSBuiltins.cpp | 1 | ||||
-rw-r--r-- | src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp | 254 | ||||
-rw-r--r-- | src/bun.js/builtins/cpp/NodeEventsBuiltins.h | 128 | ||||
-rw-r--r-- | src/bun.js/builtins/cpp/WebCoreJSBuiltins.h | 4 | ||||
-rw-r--r-- | src/bun.js/builtins/js/ConsoleObject.js | 2 | ||||
-rw-r--r-- | src/bun.js/builtins/js/NodeEvents.js | 207 | ||||
-rw-r--r-- | src/bun.js/builtins/js/ReadableStreamInternals.js | 2 | ||||
-rw-r--r-- | src/bun.js/modules/EventsModule.h | 19 | ||||
-rw-r--r-- | test/js/node/events/event-emitter.test.ts | 170 | ||||
-rw-r--r-- | test/js/node/events/node-builtins.test.js | 18 | ||||
-rw-r--r-- | test/js/node/events/node-events.node.test.ts | 386 |
16 files changed, 1116 insertions, 136 deletions
diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h index dafdaed17..cb352a76a 100644 --- a/src/bun.js/bindings/headers-cpp.h +++ b/src/bun.js/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1679200292 +//-- AUTOGENERATED FILE -- 1679596954 // clang-format off #pragma once diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index 5c727f397..f076b4719 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format off -//-- AUTOGENERATED FILE -- 1679530947 +//-- AUTOGENERATED FILE -- 1679596954 #pragma once #include <stddef.h> diff --git a/src/bun.js/bindings/webcore/JSEventEmitter.cpp b/src/bun.js/bindings/webcore/JSEventEmitter.cpp index 003313b95..aa9fb4832 100644 --- a/src/bun.js/bindings/webcore/JSEventEmitter.cpp +++ b/src/bun.js/bindings/webcore/JSEventEmitter.cpp @@ -599,31 +599,31 @@ JSC_DEFINE_HOST_FUNCTION(Events_functionOnce, RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(argument0)); } -JSC_DEFINE_HOST_FUNCTION(Events_functionOn, - (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) -{ - auto& vm = JSC::getVM(lexicalGlobalObject); - auto throwScope = DECLARE_THROW_SCOPE(vm); - UNUSED_PARAM(throwScope); - UNUSED_PARAM(callFrame); - - if (UNLIKELY(callFrame->argumentCount() < 3)) - return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)); - auto argument0 = jsEventEmitterCastFast(vm, lexicalGlobalObject, callFrame->uncheckedArgument(0)); - if (UNLIKELY(!argument0)) { - throwException(lexicalGlobalObject, throwScope, createError(lexicalGlobalObject, "Expected EventEmitter"_s)); - return JSValue::encode(JSC::jsUndefined()); - } - auto& impl = argument0->wrapped(); - auto eventType = callFrame->uncheckedArgument(1).toPropertyKey(lexicalGlobalObject); - RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); - EnsureStillAliveScope argument2 = callFrame->uncheckedArgument(2); - auto listener = convert<IDLNullable<IDLEventListener<JSEventListener>>>(*lexicalGlobalObject, argument2.value(), *argument0, [](JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) { throwArgumentMustBeObjectError(lexicalGlobalObject, scope, 2, "listener", "EventEmitter", "removeListener"); }); - RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); - auto result = JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.addListenerForBindings(WTFMove(eventType), WTFMove(listener), false, false); })); - RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); - vm.writeBarrier(argument0, argument2.value()); - return result; -} +// JSC_DEFINE_HOST_FUNCTION(Events_functionOn, +// (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) +// { +// auto& vm = JSC::getVM(lexicalGlobalObject); +// auto throwScope = DECLARE_THROW_SCOPE(vm); +// UNUSED_PARAM(throwScope); +// UNUSED_PARAM(callFrame); + +// if (UNLIKELY(callFrame->argumentCount() < 3)) +// return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)); +// auto argument0 = jsEventEmitterCastFast(vm, lexicalGlobalObject, callFrame->uncheckedArgument(0)); +// if (UNLIKELY(!argument0)) { +// throwException(lexicalGlobalObject, throwScope, createError(lexicalGlobalObject, "Expected EventEmitter"_s)); +// return JSValue::encode(JSC::jsUndefined()); +// } +// auto& impl = argument0->wrapped(); +// auto eventType = callFrame->uncheckedArgument(1).toPropertyKey(lexicalGlobalObject); +// RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); +// EnsureStillAliveScope argument2 = callFrame->uncheckedArgument(2); +// auto listener = convert<IDLNullable<IDLEventListener<JSEventListener>>>(*lexicalGlobalObject, argument2.value(), *argument0, [](JSC::JSGlobalObject& lexicalGlobalObject, JSC::ThrowScope& scope) { throwArgumentMustBeObjectError(lexicalGlobalObject, scope, 2, "listener", "EventEmitter", "removeListener"); }); +// RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); +// auto result = JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.addListenerForBindings(WTFMove(eventType), WTFMove(listener), false, false); })); +// RETURN_IF_EXCEPTION(throwScope, encodedJSValue()); +// vm.writeBarrier(argument0, argument2.value()); +// return result; +// } } diff --git a/src/bun.js/builtins/BunBuiltinNames.h b/src/bun.js/builtins/BunBuiltinNames.h index ebc2c2c05..c687e145a 100644 --- a/src/bun.js/builtins/BunBuiltinNames.h +++ b/src/bun.js/builtins/BunBuiltinNames.h @@ -147,6 +147,7 @@ using namespace JSC; macro(nextTick) \ macro(normalize) \ macro(on) \ + macro(onAsyncIterator) \ macro(once) \ macro(options) \ macro(origin) \ diff --git a/src/bun.js/builtins/README.md b/src/bun.js/builtins/README.md index c9288f47d..5aa25469f 100644 --- a/src/bun.js/builtins/README.md +++ b/src/bun.js/builtins/README.md @@ -4,7 +4,7 @@ TLDR: ```bash # Delete the built files -make clean-bindings generate-bindings && \ +make clean-bindings generate-builtins && \ # Compile all the C++ files which live in ../bindings make bindings -j10 && \ # Re-link the binary without compiling zig (so it's faster) @@ -31,7 +31,7 @@ The `js` directory is necessary for the bindings generator to work. To regenerate the builtins, run this from Bun's project root (where the `Makefile` is) ```bash -make builtins +make generate-builtins ``` You'll want to also rebuild all the C++ bindings or you will get strange crashes on start diff --git a/src/bun.js/builtins/WebCoreJSBuiltins.cpp b/src/bun.js/builtins/WebCoreJSBuiltins.cpp index f5b25a463..86dea58e3 100644 --- a/src/bun.js/builtins/WebCoreJSBuiltins.cpp +++ b/src/bun.js/builtins/WebCoreJSBuiltins.cpp @@ -39,6 +39,7 @@ #include "ImportMetaObjectBuiltins.cpp" #include "JSBufferConstructorBuiltins.cpp" #include "JSBufferPrototypeBuiltins.cpp" +#include "NodeEventsBuiltins.cpp" #include "ProcessObjectInternalsBuiltins.cpp" #include "ReadableByteStreamControllerBuiltins.cpp" #include "ReadableByteStreamInternalsBuiltins.cpp" diff --git a/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp b/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp new file mode 100644 index 000000000..1e26a934b --- /dev/null +++ b/src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2015 Igalia + * Copyright (c) 2015 Igalia S.L. + * Copyright (c) 2015 Igalia. + * Copyright (c) 2015, 2016 Canon Inc. All rights reserved. + * Copyright (c) 2015, 2016, 2017 Canon Inc. + * Copyright (c) 2016, 2020 Apple Inc. All rights reserved. + * Copyright (c) 2022 Codeblog Corp. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// DO NOT EDIT THIS FILE. It is automatically generated from JavaScript files for +// builtins by the script: Source/JavaScriptCore/Scripts/generate-js-builtins.py + +#include "config.h" +#include "NodeEventsBuiltins.h" + +#include "WebCoreJSClientData.h" +#include <JavaScriptCore/HeapInlines.h> +#include <JavaScriptCore/IdentifierInlines.h> +#include <JavaScriptCore/ImplementationVisibility.h> +#include <JavaScriptCore/Intrinsic.h> +#include <JavaScriptCore/JSCJSValueInlines.h> +#include <JavaScriptCore/JSCellInlines.h> +#include <JavaScriptCore/StructureInlines.h> +#include <JavaScriptCore/VM.h> + +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 = 4291; +static const JSC::Intrinsic s_nodeEventsOnAsyncIteratorCodeIntrinsic = JSC::NoIntrinsic; +const char* const s_nodeEventsOnAsyncIteratorCode = + "(function (emitter, event, options) {\n" \ + " \"use strict\";\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" \ + " var { AbortSignal, Object, Number } = globalThis;\n" \ + "\n" \ + " if (@isUndefinedOrNull(options)) options = {};\n" \ + "\n" \ + " //\n" \ + " var signal = options.signal;\n" \ + " if (!@isUndefinedOrNull(signal) && !(signal instanceof AbortSignal))\n" \ + " @throwTypeError(\"options.signal must be an AbortSignal\");\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" \ + " @throwRangeError(\"options.highWatermark must be >= 1\");\n" \ + "\n" \ + " var lowWatermark = options.lowWatermark ?? 1;\n" \ + " if (lowWatermark < 1) \n" \ + " @throwRangeError(\"options.lowWatermark must be >= 1\");\n" \ + "\n" \ + " var unconsumedEvents = @createFIFO();\n" \ + " var unconsumedPromises = @createFIFO();\n" \ + "\n" \ + " var paused = false;\n" \ + " var error = null;\n" \ + " var finished = false;\n" \ + " var size = 0;\n" \ + "\n" \ + " function abortListener() {\n" \ + " errorHandler(new AbortError(@undefined, { cause: signal?.reason }));\n" \ + " }\n" \ + "\n" \ + " function eventHandler(value) {\n" \ + " if (unconsumedPromises.isEmpty()) {\n" \ + " size++;\n" \ + " if (!paused && size > highWatermark) {\n" \ + " paused = true;\n" \ + " emitter.pause();\n" \ + " }\n" \ + " unconsumedEvents.push(value);\n" \ + " } else unconsumedPromises.shift().@resolve.@call(@undefined, { value, done: false });\n" \ + " }\n" \ + "\n" \ + " function closeHandler() {\n" \ + " removeAllListeners(listeners);\n" \ + " finished = true;\n" \ + " var doneResult = { value: @undefined, done: true };\n" \ + " while (!unconsumedPromises.isEmpty()) {\n" \ + " unconsumedPromises.shift().@resolve.@call(@undefined, doneResult);\n" \ + " }\n" \ + " \n" \ + " return @createFulfilledPromise(doneResult);\n" \ + " }\n" \ + "\n" \ + " function errorHandler(err) {\n" \ + " if (unconsumedPromises.isEmpty()) error = err;\n" \ + " else unconsumedPromises.shift().@reject.@call(@undefined, err);\n" \ + " \n" \ + " closeHandler();\n" \ + " }\n" \ + " \n" \ + " function addEventListener(emitter, event, handler) {\n" \ + " emitter.on(event, handler);\n" \ + " @arrayPush(listeners, emitter, event, handler);\n" \ + " }\n" \ + " \n" \ + " function removeAllListeners() {\n" \ + " while (listeners.length > 0) {\n" \ + " var [emitter, event, handler] = @arrayPop(listeners);\n" \ + " emitter.off(event, handler);\n" \ + " }\n" \ + " }\n" \ + "\n" \ + " var iterator = async function* NodeEventsOnAsyncIterator() {\n" \ + " //\n" \ + " if (size) {\n" \ + " var value = unconsumedEvents.shift();\n" \ + " size--;\n" \ + " if (paused && size < lowWatermark) {\n" \ + " emitter.resume();\n" \ + " paused = false;\n" \ + " }\n" \ + " yield @createFulfilledPromise({ value, done: false });\n" \ + " }\n" \ + "\n" \ + " //\n" \ + " //\n" \ + " //\n" \ + " if (error) {\n" \ + " var p = @Promise.@reject(error);\n" \ + " //\n" \ + " error = null;\n" \ + " yield p;\n" \ + " }\n" \ + "\n" \ + " //\n" \ + " if (finished) yield closeHandler();\n" \ + "\n" \ + " //\n" \ + " var nextEventPromiseCapability = @newPromiseCapability(@Promise);\n" \ + " unconsumedPromises.push(nextEventPromiseCapability);\n" \ + " yield nextEventPromiseCapability.@promise;\n" \ + " };\n" \ + "\n" \ + " //\n" \ + " Object.defineProperties(iterator, {\n" \ + " \"throw\": {\n" \ + " value: (err) => {\n" \ + " //\n" \ + " if (err === undefined || err === null || !(err instanceof Error)) {\n" \ + " @throwTypeError(\"The argument must be an instance of Error\");\n" \ + " }\n" \ + " errorHandler(err);\n" \ + " },\n" \ + " },\n" \ + " \"return\": {\n" \ + " value: () => {\n" \ + " return 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" \ + " var listeners = [];\n" \ + " addEventListener(emitter, event, eventHandler);\n" \ + " if (event !== \"error\" && typeof emitter.on === \"function\") {\n" \ + " addEventListener(emitter, \"error\", errorHandler);\n" \ + " }\n" \ + " var closeEvents = options?.close;\n" \ + " if (closeEvents?.length) {\n" \ + " for (var i = 0; i < closeEvents.length; i++) {\n" \ + " addEventListener(emitter, closeEvents[i], closeHandler);\n" \ + " }\n" \ + " }\n" \ + "\n" \ + " if (signal)\n" \ + " signal.once(\"abort\", abortListener);\n" \ + "\n" \ + " return iterator;\n" \ + "})\n" \ +; + + +#define DEFINE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \ +JSC::FunctionExecutable* codeName##Generator(JSC::VM& vm) \ +{\ + JSVMClientData* clientData = static_cast<JSVMClientData*>(vm.clientData); \ + return clientData->builtinFunctions().nodeEventsBuiltins().codeName##Executable()->link(vm, nullptr, clientData->builtinFunctions().nodeEventsBuiltins().codeName##Source(), std::nullopt, s_##codeName##Intrinsic); \ +} +WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(DEFINE_BUILTIN_GENERATOR) +#undef DEFINE_BUILTIN_GENERATOR + + +} // namespace WebCore diff --git a/src/bun.js/builtins/cpp/NodeEventsBuiltins.h b/src/bun.js/builtins/cpp/NodeEventsBuiltins.h new file mode 100644 index 000000000..11b70ea92 --- /dev/null +++ b/src/bun.js/builtins/cpp/NodeEventsBuiltins.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2015 Igalia + * Copyright (c) 2015 Igalia S.L. + * Copyright (c) 2015 Igalia. + * Copyright (c) 2015, 2016 Canon Inc. All rights reserved. + * Copyright (c) 2015, 2016, 2017 Canon Inc. + * Copyright (c) 2016, 2020 Apple Inc. All rights reserved. + * Copyright (c) 2022 Codeblog Corp. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// DO NOT EDIT THIS FILE. It is automatically generated from JavaScript files for +// builtins by the script: Source/JavaScriptCore/Scripts/generate-js-builtins.py + +#pragma once + +#include <JavaScriptCore/BuiltinUtils.h> +#include <JavaScriptCore/Identifier.h> +#include <JavaScriptCore/JSFunction.h> +#include <JavaScriptCore/UnlinkedFunctionExecutable.h> + +namespace JSC { +class FunctionExecutable; +} + +namespace WebCore { + +/* NodeEvents */ +extern const char* const s_nodeEventsOnAsyncIteratorCode; +extern const int s_nodeEventsOnAsyncIteratorCodeLength; +extern const JSC::ConstructAbility s_nodeEventsOnAsyncIteratorCodeConstructAbility; +extern const JSC::ConstructorKind s_nodeEventsOnAsyncIteratorCodeConstructorKind; +extern const JSC::ImplementationVisibility s_nodeEventsOnAsyncIteratorCodeImplementationVisibility; + +#define WEBCORE_FOREACH_NODEEVENTS_BUILTIN_DATA(macro) \ + macro(onAsyncIterator, nodeEventsOnAsyncIterator, 3) \ + +#define WEBCORE_BUILTIN_NODEEVENTS_ONASYNCITERATOR 1 + +#define WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(macro) \ + macro(nodeEventsOnAsyncIteratorCode, onAsyncIterator, ASCIILiteral(), s_nodeEventsOnAsyncIteratorCodeLength) \ + +#define WEBCORE_FOREACH_NODEEVENTS_BUILTIN_FUNCTION_NAME(macro) \ + macro(onAsyncIterator) \ + +#define DECLARE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \ + JSC::FunctionExecutable* codeName##Generator(JSC::VM&); + +WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(DECLARE_BUILTIN_GENERATOR) +#undef DECLARE_BUILTIN_GENERATOR + +class NodeEventsBuiltinsWrapper : private JSC::WeakHandleOwner { +public: + explicit NodeEventsBuiltinsWrapper(JSC::VM& vm) + : m_vm(vm) + WEBCORE_FOREACH_NODEEVENTS_BUILTIN_FUNCTION_NAME(INITIALIZE_BUILTIN_NAMES) +#define INITIALIZE_BUILTIN_SOURCE_MEMBERS(name, functionName, overriddenName, length) , m_##name##Source(JSC::makeSource(StringImpl::createWithoutCopying(s_##name, length), { })) + WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(INITIALIZE_BUILTIN_SOURCE_MEMBERS) +#undef INITIALIZE_BUILTIN_SOURCE_MEMBERS + { + } + +#define EXPOSE_BUILTIN_EXECUTABLES(name, functionName, overriddenName, length) \ + JSC::UnlinkedFunctionExecutable* name##Executable(); \ + const JSC::SourceCode& name##Source() const { return m_##name##Source; } + WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(EXPOSE_BUILTIN_EXECUTABLES) +#undef EXPOSE_BUILTIN_EXECUTABLES + + WEBCORE_FOREACH_NODEEVENTS_BUILTIN_FUNCTION_NAME(DECLARE_BUILTIN_IDENTIFIER_ACCESSOR) + + void exportNames(); + +private: + JSC::VM& m_vm; + + WEBCORE_FOREACH_NODEEVENTS_BUILTIN_FUNCTION_NAME(DECLARE_BUILTIN_NAMES) + +#define DECLARE_BUILTIN_SOURCE_MEMBERS(name, functionName, overriddenName, length) \ + JSC::SourceCode m_##name##Source;\ + JSC::Weak<JSC::UnlinkedFunctionExecutable> m_##name##Executable; + WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(DECLARE_BUILTIN_SOURCE_MEMBERS) +#undef DECLARE_BUILTIN_SOURCE_MEMBERS + +}; + +#define DEFINE_BUILTIN_EXECUTABLES(name, functionName, overriddenName, length) \ +inline JSC::UnlinkedFunctionExecutable* NodeEventsBuiltinsWrapper::name##Executable() \ +{\ + if (!m_##name##Executable) {\ + JSC::Identifier executableName = functionName##PublicName();\ + if (overriddenName)\ + executableName = JSC::Identifier::fromString(m_vm, overriddenName);\ + m_##name##Executable = JSC::Weak<JSC::UnlinkedFunctionExecutable>(JSC::createBuiltinExecutable(m_vm, m_##name##Source, executableName, s_##name##ImplementationVisibility, s_##name##ConstructorKind, s_##name##ConstructAbility), this, &m_##name##Executable);\ + }\ + return m_##name##Executable.get();\ +} +WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(DEFINE_BUILTIN_EXECUTABLES) +#undef DEFINE_BUILTIN_EXECUTABLES + +inline void NodeEventsBuiltinsWrapper::exportNames() +{ +#define EXPORT_FUNCTION_NAME(name) m_vm.propertyNames->appendExternalName(name##PublicName(), name##PrivateName()); + WEBCORE_FOREACH_NODEEVENTS_BUILTIN_FUNCTION_NAME(EXPORT_FUNCTION_NAME) +#undef EXPORT_FUNCTION_NAME +} + +} // namespace WebCore diff --git a/src/bun.js/builtins/cpp/WebCoreJSBuiltins.h b/src/bun.js/builtins/cpp/WebCoreJSBuiltins.h index 598e0948e..0737f1a70 100644 --- a/src/bun.js/builtins/cpp/WebCoreJSBuiltins.h +++ b/src/bun.js/builtins/cpp/WebCoreJSBuiltins.h @@ -41,6 +41,7 @@ #include "ImportMetaObjectBuiltins.h" #include "JSBufferConstructorBuiltins.h" #include "JSBufferPrototypeBuiltins.h" +#include "NodeEventsBuiltins.h" #include "ProcessObjectInternalsBuiltins.h" #include "ReadableByteStreamControllerBuiltins.h" #include "ReadableByteStreamInternalsBuiltins.h" @@ -71,6 +72,7 @@ public: , m_importMetaObjectBuiltins(m_vm) , m_jsBufferConstructorBuiltins(m_vm) , m_jsBufferPrototypeBuiltins(m_vm) + , m_nodeEventsBuiltins(m_vm) , m_processObjectInternalsBuiltins(m_vm) , m_readableByteStreamControllerBuiltins(m_vm) , m_readableByteStreamInternalsBuiltins(m_vm) @@ -101,6 +103,7 @@ public: ImportMetaObjectBuiltinsWrapper& importMetaObjectBuiltins() { return m_importMetaObjectBuiltins; } JSBufferConstructorBuiltinsWrapper& jsBufferConstructorBuiltins() { return m_jsBufferConstructorBuiltins; } JSBufferPrototypeBuiltinsWrapper& jsBufferPrototypeBuiltins() { return m_jsBufferPrototypeBuiltins; } + NodeEventsBuiltinsWrapper& nodeEventsBuiltins() { return m_nodeEventsBuiltins; } ProcessObjectInternalsBuiltinsWrapper& processObjectInternalsBuiltins() { return m_processObjectInternalsBuiltins; } ReadableByteStreamControllerBuiltinsWrapper& readableByteStreamControllerBuiltins() { return m_readableByteStreamControllerBuiltins; } ReadableByteStreamInternalsBuiltinsWrapper& readableByteStreamInternalsBuiltins() { return m_readableByteStreamInternalsBuiltins; } @@ -126,6 +129,7 @@ private: ImportMetaObjectBuiltinsWrapper m_importMetaObjectBuiltins; JSBufferConstructorBuiltinsWrapper m_jsBufferConstructorBuiltins; JSBufferPrototypeBuiltinsWrapper m_jsBufferPrototypeBuiltins; + NodeEventsBuiltinsWrapper m_nodeEventsBuiltins; ProcessObjectInternalsBuiltinsWrapper m_processObjectInternalsBuiltins; ReadableByteStreamControllerBuiltinsWrapper m_readableByteStreamControllerBuiltins; ReadableByteStreamInternalsBuiltinsWrapper m_readableByteStreamInternalsBuiltins; diff --git a/src/bun.js/builtins/js/ConsoleObject.js b/src/bun.js/builtins/js/ConsoleObject.js index 8601748c6..4eda757b6 100644 --- a/src/bun.js/builtins/js/ConsoleObject.js +++ b/src/bun.js/builtins/js/ConsoleObject.js @@ -112,4 +112,4 @@ function write(input) { writer.flush(true); return wrote; -}
\ No newline at end of file +} diff --git a/src/bun.js/builtins/js/NodeEvents.js b/src/bun.js/builtins/js/NodeEvents.js new file mode 100644 index 000000000..b94d30725 --- /dev/null +++ b/src/bun.js/builtins/js/NodeEvents.js @@ -0,0 +1,207 @@ +/* + * Copyright 2022 Codeblog Corp. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +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 } = globalThis; + + if (@isUndefinedOrNull(options)) options = {}; + + // Parameters validation + var signal = options.signal; + 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 }); + } + + var highWatermark = options.highWatermark ?? Number.MAX_SAFE_INTEGER; + if (highWatermark < 1) + @throwRangeError("options.highWatermark must be >= 1"); + + var lowWatermark = options.lowWatermark ?? 1; + if (lowWatermark < 1) + @throwRangeError("options.lowWatermark must be >= 1"); + + var unconsumedEvents = @createFIFO(); + var unconsumedPromises = @createFIFO(); + + var paused = false; + var error = null; + var finished = false; + var size = 0; + + function abortListener() { + errorHandler(new AbortError(@undefined, { cause: signal?.reason })); + } + + function eventHandler(value) { + if (unconsumedPromises.isEmpty()) { + size++; + if (!paused && size > highWatermark) { + paused = true; + emitter.pause(); + } + unconsumedEvents.push(value); + } else unconsumedPromises.shift().@resolve.@call(@undefined, { value, done: false }); + } + + function closeHandler() { + removeAllListeners(listeners); + finished = true; + var doneResult = { value: @undefined, done: true }; + while (!unconsumedPromises.isEmpty()) { + unconsumedPromises.shift().@resolve.@call(@undefined, doneResult); + } + + return @createFulfilledPromise(doneResult); + } + + function errorHandler(err) { + if (unconsumedPromises.isEmpty()) error = err; + else unconsumedPromises.shift().@reject.@call(@undefined, err); + + closeHandler(); + } + + function addEventListener(emitter, event, handler) { + emitter.on(event, handler); + @arrayPush(listeners, emitter, event, handler); + } + + function removeAllListeners() { + while (listeners.length > 0) { + var [emitter, event, handler] = @arrayPop(listeners); + emitter.off(event, handler); + } + } + + var iterator = async function* NodeEventsOnAsyncIterator() { + // First, we consume all unread events + if (size) { + var value = unconsumedEvents.shift(); + size--; + if (paused && size < lowWatermark) { + emitter.resume(); + paused = false; + } + yield @createFulfilledPromise({ value, done: false }); + } + + // Then we error, if an error happened + // This happens one time if at all, because after 'error' + // we stop listening + if (error) { + var p = @Promise.@reject(error); + // Only the first element errors + error = null; + yield p; + } + + // If the iterator is finished, resolve to done + if (finished) yield closeHandler(); + + // Wait until an event happens + var nextEventPromiseCapability = @newPromiseCapability(@Promise); + unconsumedPromises.push(nextEventPromiseCapability); + yield nextEventPromiseCapability.@promise; + }; + + // 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(); + } + }, + // [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 + var listeners = []; + addEventListener(emitter, event, eventHandler); + if (event !== "error" && typeof emitter.on === "function") { + addEventListener(emitter, "error", errorHandler); + } + var closeEvents = options?.close; + if (closeEvents?.length) { + for (var i = 0; i < closeEvents.length; i++) { + addEventListener(emitter, closeEvents[i], closeHandler); + } + } + + if (signal) + signal.once("abort", abortListener); + + return iterator; +} diff --git a/src/bun.js/builtins/js/ReadableStreamInternals.js b/src/bun.js/builtins/js/ReadableStreamInternals.js index ca7a1c355..e98d675b2 100644 --- a/src/bun.js/builtins/js/ReadableStreamInternals.js +++ b/src/bun.js/builtins/js/ReadableStreamInternals.js @@ -2206,4 +2206,4 @@ function readableStreamDefineLazyIterators(prototype) { @Object.@defineProperty(prototype, asyncIterator, { value: createAsyncIterator }); @Object.@defineProperty(prototype, "values", { value: createValues }); return prototype; -}
\ No newline at end of file +} diff --git a/src/bun.js/modules/EventsModule.h b/src/bun.js/modules/EventsModule.h index 7d53ff838..e60624ac9 100644 --- a/src/bun.js/modules/EventsModule.h +++ b/src/bun.js/modules/EventsModule.h @@ -28,10 +28,6 @@ inline void generateEventsSourceCode(JSC::JSGlobalObject *lexicalGlobalObject, exportValues.append(JSC::JSFunction::create( vm, lexicalGlobalObject, 0, MAKE_STATIC_STRING_IMPL("once"), Events_functionOnce, ImplementationVisibility::Public)); - exportNames.append(JSC::Identifier::fromString(vm, "on"_s)); - exportValues.append(JSC::JSFunction::create( - vm, lexicalGlobalObject, 0, MAKE_STATIC_STRING_IMPL("on"), - Events_functionOn, ImplementationVisibility::Public)); exportNames.append( JSC::Identifier::fromString(vm, "captureRejectionSymbol"_s)); exportValues.append(Symbol::create( @@ -41,16 +37,23 @@ inline void generateEventsSourceCode(JSC::JSGlobalObject *lexicalGlobalObject, jsCast<JSFunction *>(WebCore::JSEventEmitter::getConstructor( vm, reinterpret_cast<Zig::GlobalObject *>(globalObject))); + for (size_t i = 0; i < exportNames.size(); i++) { + eventEmitterModuleCJS->putDirect(vm, exportNames[i], exportValues.at(i), 0); + } + + exportNames.append(JSC::Identifier::fromString(vm, "on"_s)); + auto *onAsyncIterFnPtr = eventEmitterModuleCJS->putDirectBuiltinFunction( + vm, globalObject, JSC::Identifier::fromString(vm, "on"_s), + nodeEventsOnAsyncIteratorCodeGenerator(vm), + PropertyAttribute::Builtin | PropertyAttribute::DontDelete); + exportValues.append(onAsyncIterFnPtr); + eventEmitterModuleCJS->putDirect( vm, PropertyName( Identifier::fromUid(vm.symbolRegistry().symbolForKey("CommonJS"_s))), jsNumber(0), 0); - for (size_t i = 0; i < exportNames.size(); i++) { - eventEmitterModuleCJS->putDirect(vm, exportNames[i], exportValues.at(i), 0); - } - exportNames.append(JSC::Identifier::fromString(vm, "default"_s)); exportValues.append(eventEmitterModuleCJS); } diff --git a/test/js/node/events/event-emitter.test.ts b/test/js/node/events/event-emitter.test.ts index 401ccf605..13e739032 100644 --- a/test/js/node/events/event-emitter.test.ts +++ b/test/js/node/events/event-emitter.test.ts @@ -1,11 +1,61 @@ import { test, describe, expect, it } from "bun:test"; import { heapStats } from "bun:jsc"; import { expectMaxObjectTypeCount, gc } from "harness"; + // this is also testing that imports with default and named imports in the same statement work // our transpiler transform changes this to a var with import.meta.require import EventEmitter, { getEventListeners, captureRejectionSymbol } from "node:events"; +const waysOfCreating = [ + () => Object.create(EventEmitter.prototype), + () => new EventEmitter(), + () => new (class extends EventEmitter {})(), + () => { + class MyEmitter extends EventEmitter {} + return new MyEmitter(); + }, + () => { + var foo = {}; + Object.setPrototypeOf(foo, EventEmitter.prototype); + return foo; + }, + () => { + function FakeEmitter(this: any) { + return EventEmitter.call(this); + } + Object.setPrototypeOf(FakeEmitter.prototype, EventEmitter.prototype); + Object.setPrototypeOf(FakeEmitter, EventEmitter); + return new (FakeEmitter as any)(); + }, + () => { + const FakeEmitter: any = function FakeEmitter(this: any) { + EventEmitter.call(this); + } as any; + Object.assign(FakeEmitter.prototype, EventEmitter.prototype); + Object.assign(FakeEmitter, EventEmitter); + return new FakeEmitter(); + }, + () => { + var foo = {}; + Object.assign(foo, EventEmitter.prototype); + return foo; + }, +]; + describe("EventEmitter", () => { + const emitters = [EventEmitter, require("events")]; + it("should emit events", () => { + for (let Emitter of emitters) { + const emitter = new Emitter(); + var called = false; + const listener = () => { + called = true; + }; + emitter.on("test", listener); + emitter.emit("test"); + expect(called).toBe(true); + } + }); it("captureRejectionSymbol", () => { expect(EventEmitter.captureRejectionSymbol).toBeDefined(); expect(captureRejectionSymbol).toBeDefined(); @@ -74,91 +124,55 @@ describe("EventEmitter", () => { emitter.on("wow", () => done()); setTimeout(() => emitter.emit("wow"), 1); }); -}); - -const waysOfCreating = [ - () => Object.create(EventEmitter.prototype), - () => new EventEmitter(), - () => new (class extends EventEmitter {})(), - () => { - class MyEmitter extends EventEmitter {} - return new MyEmitter(); - }, - () => { - var foo = {}; - Object.setPrototypeOf(foo, EventEmitter.prototype); - return foo; - }, - () => { - function FakeEmitter(this: any) { - return EventEmitter.call(this); - } - Object.setPrototypeOf(FakeEmitter.prototype, EventEmitter.prototype); - Object.setPrototypeOf(FakeEmitter, EventEmitter); - return new (FakeEmitter as any)(); - }, - () => { - const FakeEmitter: any = function FakeEmitter(this: any) { - EventEmitter.call(this); - } as any; - Object.assign(FakeEmitter.prototype, EventEmitter.prototype); - Object.assign(FakeEmitter, EventEmitter); - return new FakeEmitter(); - }, - () => { - var foo = {}; - Object.assign(foo, EventEmitter.prototype); - return foo; - }, -]; -for (let create of waysOfCreating) { - it(`${create.toString().slice(10, 40).replaceAll("\n", "\\n").trim()} should work`, () => { - var myEmitter = create(); - var called = false; - (myEmitter as EventEmitter).once("event", function () { - called = true; - // @ts-ignore - expect(this).toBe(myEmitter); + for (let create of waysOfCreating) { + it(`${create.toString().slice(10, 40).replaceAll("\n", "\\n").trim()} should work`, () => { + var myEmitter = create(); + var called = false; + (myEmitter as EventEmitter).once("event", function () { + called = true; + // @ts-ignore + expect(this).toBe(myEmitter); + }); + var firstEvents = myEmitter._events; + expect(myEmitter.listenerCount("event")).toBe(1); + + expect(myEmitter.emit("event")).toBe(true); + expect(myEmitter.listenerCount("event")).toBe(0); + + expect(firstEvents).toBe(myEmitter._events); + expect(called).toBe(true); }); - var firstEvents = myEmitter._events; - expect(myEmitter.listenerCount("event")).toBe(1); - - expect(myEmitter.emit("event")).toBe(true); - expect(myEmitter.listenerCount("event")).toBe(0); + } - expect(firstEvents).toBe(myEmitter._events); - expect(called).toBe(true); + test("EventEmitter.on", () => { + var myEmitter = new EventEmitter(); + expect(myEmitter.on("foo", () => {})).toBe(myEmitter); }); -} -test("EventEmitter.on", () => { - var myEmitter = new EventEmitter(); - expect(myEmitter.on("foo", () => {})).toBe(myEmitter); -}); - -test("EventEmitter.off", () => { - var myEmitter = new EventEmitter(); - expect(myEmitter.off("foo", () => {})).toBe(myEmitter); -}); + test("EventEmitter.off", () => { + var myEmitter = new EventEmitter(); + expect(myEmitter.off("foo", () => {})).toBe(myEmitter); + }); -// Internally, EventEmitter has a JSC::Weak with the thisValue of the listener -test("EventEmitter GCs", async () => { - gc(); + // Internally, EventEmitter has a JSC::Weak with the thisValue of the listener + test("EventEmitter GCs", async () => { + gc(); - const startCount = heapStats().objectTypeCounts["EventEmitter"] ?? 0; - (function () { - function EventEmitterSubclass(this: any) { - EventEmitter.call(this); - } + const startCount = heapStats().objectTypeCounts["EventEmitter"] ?? 0; + (function () { + function EventEmitterSubclass(this: any) { + EventEmitter.call(this); + } - Object.setPrototypeOf(EventEmitterSubclass.prototype, EventEmitter.prototype); - Object.setPrototypeOf(EventEmitterSubclass, EventEmitter); - // @ts-ignore - var myEmitter = new EventEmitterSubclass(); - myEmitter.on("foo", () => {}); - myEmitter.emit("foo"); - })(); + Object.setPrototypeOf(EventEmitterSubclass.prototype, EventEmitter.prototype); + Object.setPrototypeOf(EventEmitterSubclass, EventEmitter); + // @ts-ignore + var myEmitter = new EventEmitterSubclass(); + myEmitter.on("foo", () => {}); + myEmitter.emit("foo"); + })(); - await expectMaxObjectTypeCount(expect, "EventEmitter", startCount); + await expectMaxObjectTypeCount(expect, "EventEmitter", startCount); + }); }); diff --git a/test/js/node/events/node-builtins.test.js b/test/js/node/events/node-builtins.test.js deleted file mode 100644 index 67050f31a..000000000 --- a/test/js/node/events/node-builtins.test.js +++ /dev/null @@ -1,18 +0,0 @@ -import { describe, it, expect } from "bun:test"; - -import { EventEmitter } from "events"; -var emitters = [EventEmitter, require("events")]; -describe("EventEmitter", () => { - it("should emit events", () => { - for (let Emitter of emitters) { - const emitter = new Emitter(); - var called = false; - const listener = () => { - called = true; - }; - emitter.on("test", listener); - emitter.emit("test"); - expect(called).toBe(true); - } - }); -}); diff --git a/test/js/node/events/node-events.node.test.ts b/test/js/node/events/node-events.node.test.ts new file mode 100644 index 000000000..10b109578 --- /dev/null +++ b/test/js/node/events/node-events.node.test.ts @@ -0,0 +1,386 @@ +import { EventEmitter, on } from "node:events"; +import { createTest } from "node-harness"; + +const { expect, assert, describe, it, createCallCheckCtx, createDoneDotAll } = createTest(import.meta.path); +// const NodeEventTarget = globalThis.EventTarget; + +describe("node:events.on (EE async iterator)", () => { + 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); + }); + + const iterable = on(ee, "foo"); + const expected = [["bar"], [42]]; + + for await (const event of iterable) { + const current = expected.shift(); + + assert.deepStrictEqual(current, event); + + if (expected.length === 0) { + break; + } + } + assert.strictEqual(ee.listenerCount("foo"), 0); + assert.strictEqual(ee.listenerCount("error"), 0); + }); + + // async function invalidArgType() { + // assert.throws( + // () => on({}, "foo"), + // common.expectsError({ + // code: "ERR_INVALID_ARG_TYPE", + // name: "TypeError", + // }), + // ); + // } + + 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"); + let looped = false; + let thrown = false; + + try { + // eslint-disable-next-line no-unused-vars + for await (const event of iterable) { + looped = true; + } + } catch (err) { + thrown = true; + assert.strictEqual(err, _err); + } + assert.strictEqual(thrown, true); + assert.strictEqual(looped, false); + }); + + // async function errorDelayed() { + // const ee = new EventEmitter(); + // const _err = new Error("kaboom"); + // process.nextTick(() => { + // ee.emit("foo", 42); + // ee.emit("error", _err); + // }); + + // const iterable = on(ee, "foo"); + // const expected = [[42]]; + // let thrown = false; + + // try { + // for await (const event of iterable) { + // const current = expected.shift(); + // assert.deepStrictEqual(current, event); + // } + // } catch (err) { + // thrown = true; + // assert.strictEqual(err, _err); + // } + // assert.strictEqual(thrown, true); + // assert.strictEqual(ee.listenerCount("foo"), 0); + // assert.strictEqual(ee.listenerCount("error"), 0); + // } + + // async function throwInLoop() { + // const ee = new EventEmitter(); + // const _err = new Error("kaboom"); + + // process.nextTick(() => { + // ee.emit("foo", 42); + // }); + + // try { + // for await (const event of on(ee, "foo")) { + // assert.deepStrictEqual(event, [42]); + // throw _err; + // } + // } catch (err) { + // assert.strictEqual(err, _err); + // } + + // assert.strictEqual(ee.listenerCount("foo"), 0); + // assert.strictEqual(ee.listenerCount("error"), 0); + // } + + // async function next() { + // const ee = new EventEmitter(); + // const iterable = on(ee, "foo"); + + // process.nextTick(function () { + // ee.emit("foo", "bar"); + // ee.emit("foo", 42); + // iterable.return(); + // }); + + // 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); + // } + + // async function iterableThrow() { + // const ee = new EventEmitter(); + // const iterable = on(ee, "foo"); + + // process.nextTick(() => { + // ee.emit("foo", "bar"); + // ee.emit("foo", 42); // lost in the queue + // iterable.throw(_err); + // }); + + // const _err = new Error("kaboom"); + // let thrown = false; + + // assert.throws( + // () => { + // // No argument + // iterable.throw(); + // }, + // { + // message: 'The "EventEmitter.AsyncIterator" property must be' + " an instance of Error. Received undefined", + // name: "TypeError", + // }, + // ); + + // const expected = [["bar"], [42]]; + + // try { + // for await (const event of iterable) { + // assert.deepStrictEqual(event, expected.shift()); + // } + // } catch (err) { + // thrown = true; + // assert.strictEqual(err, _err); + // } + // assert.strictEqual(thrown, true); + // assert.strictEqual(expected.length, 0); + // assert.strictEqual(ee.listenerCount("foo"), 0); + // assert.strictEqual(ee.listenerCount("error"), 0); + // } + + // // async function eventTarget() { + // // const et = new EventTarget(); + // // const tick = () => et.dispatchEvent(new Event("tick")); + // // const interval = setInterval(tick, 0); + // // let count = 0; + // // for await (const [event] of on(et, "tick")) { + // // count++; + // // assert.strictEqual(event.type, "tick"); + // // if (count >= 5) { + // // break; + // // } + // // } + // // assert.strictEqual(count, 5); + // // clearInterval(interval); + // // } + + // async function errorListenerCount() { + // const et = new EventEmitter(); + // on(et, "foo"); + // assert.strictEqual(et.listenerCount("error"), 1); + // } + + // // async function nodeEventTarget() { + // // const et = new NodeEventTarget(); + // // const tick = () => et.dispatchEvent(new Event("tick")); + // // const interval = setInterval(tick, 0); + // // let count = 0; + // // for await (const [event] of on(et, "tick")) { + // // count++; + // // assert.strictEqual(event.type, "tick"); + // // if (count >= 5) { + // // break; + // // } + // // } + // // assert.strictEqual(count, 5); + // // clearInterval(interval); + // // } + + // async function abortableOnBefore() { + // it("should "); + // const ee = new EventEmitter(); + // const abortedSignal = AbortSignal.abort(); + // [1, {}, null, false, "hi"].forEach((signal: any) => { + // assert.throws(() => on(ee, "foo", { signal }), { + // code: "ERR_INVALID_ARG_TYPE", + // }); + // }); + // assert.throws(() => on(ee, "foo", { signal: abortedSignal }), { + // name: "AbortError", + // }); + // } + + // async function eventTargetAbortableOnBefore() { + // const et = new EventTarget(); + // const abortedSignal = AbortSignal.abort(); + // [1, {}, null, false, "hi"].forEach(signal => { + // assert.throws(() => on(et, "foo", { signal }), { + // code: "ERR_INVALID_ARG_TYPE", + // }); + // }); + // assert.throws(() => on(et, "foo", { signal: abortedSignal }), { + // name: "AbortError", + // }); + // } + + // it("should NOT throw an `AbortError` when done iterating over events", async done => { + // const ee = new EventEmitter(); + // const ac = new AbortController(); + + // const i = setInterval(() => ee.emit("foo", "foo"), 1); + // let count = 0; + + // async function foo() { + // for await (const f of on(ee, "foo", { signal: ac.signal })) { + // assert.strictEqual(f[0], "foo"); + // if (++count === 5) break; + // } + // ac.abort(); // No error will occur + // } + + // foo().finally(() => { + // clearInterval(i); + // expect(true).toBe(true); + // done(); + // }); + // }); + + // it("should throw an `AbortError` when NOT done iterating over events", async done => { + // const createDone = createDoneDotAll(done); + // const { mustCall, closeTimers } = createCallCheckCtx(createDone()); + // const finalDone = createDone(); + + // const ee = new EventEmitter(); + // const ac = new AbortController(); + + // const i = setInterval(() => ee.emit("foo", "foo"), 10); + + // async function foo() { + // for await (const f of on(ee, "foo", { signal: ac.signal })) { + // assert.strictEqual(f, "foo"); + // } + // } + + // foo() + // .catch( + // mustCall(error => { + // assert.strictEqual(error.name, "AbortError"); + // }), + // ) + // .finally(() => { + // clearInterval(i); + // finalDone(); + // closeTimers(); + // }); + + // process.nextTick(() => ac.abort()); + // }); + + // async function eventTargetAbortableOnAfter() { + // const et = new EventTarget(); + // const ac = new AbortController(); + + // const i = setInterval(() => et.dispatchEvent(new Event("foo")), 10); + + // async function foo() { + // for await (const f of on(et, "foo", { signal: ac.signal })) { + // assert(f); + // } + // } + + // foo() + // .catch( + // common.mustCall(error => { + // assert.strictEqual(error.name, "AbortError"); + // }), + // ) + // .finally(() => { + // clearInterval(i); + // }); + + // process.nextTick(() => ac.abort()); + // } + + // async function eventTargetAbortableOnAfter2() { + // const et = new EventTarget(); + // const ac = new AbortController(); + + // const i = setInterval(() => et.dispatchEvent(new Event("foo")), 10); + + // async function foo() { + // for await (const f of on(et, "foo", { signal: ac.signal })) { + // assert(f); + // // Cancel after a single event has been triggered. + // ac.abort(); + // } + // } + + // foo() + // .catch( + // common.mustCall(error => { + // assert.strictEqual(error.name, "AbortError"); + // }), + // ) + // .finally(() => { + // clearInterval(i); + // }); + // } +}); |