mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Compare commits
6 Commits
fix-redis-
...
derrick/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d115f278de | ||
|
|
f3d593c9bd | ||
|
|
671074ac8a | ||
|
|
bfd92d059c | ||
|
|
b2625b4ab9 | ||
|
|
91f02989dc |
@@ -1,4 +1,4 @@
|
||||
//-- AUTOGENERATED FILE -- 1679200292
|
||||
//-- AUTOGENERATED FILE -- 1680843050
|
||||
// clang-format off
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// clang-format off
|
||||
//-- AUTOGENERATED FILE -- 1679530947
|
||||
//-- AUTOGENERATED FILE -- 1680843050
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -573,57 +573,4 @@ JSC_DEFINE_HOST_FUNCTION(Events_functionListenerCount,
|
||||
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(JSC::jsNumber(impl.listenerCount(eventType))));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(Events_functionOnce,
|
||||
(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());
|
||||
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
|
||||
vm.writeBarrier(argument0, argument2.value());
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ namespace WebCore {
|
||||
|
||||
JSC_DECLARE_HOST_FUNCTION(Events_functionGetEventListeners);
|
||||
JSC_DECLARE_HOST_FUNCTION(Events_functionListenerCount);
|
||||
JSC_DECLARE_HOST_FUNCTION(Events_functionOnce);
|
||||
JSC_DECLARE_HOST_FUNCTION(Events_functionOn);
|
||||
|
||||
class JSEventEmitter : public JSDOMWrapper<EventEmitter> {
|
||||
public:
|
||||
|
||||
@@ -147,6 +147,7 @@ using namespace JSC;
|
||||
macro(nextTick) \
|
||||
macro(normalize) \
|
||||
macro(on) \
|
||||
macro(onAsyncIterator) \
|
||||
macro(once) \
|
||||
macro(options) \
|
||||
macro(origin) \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
323
src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp
Normal file
323
src/bun.js/builtins/cpp/NodeEventsBuiltins.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* 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 = 4565;
|
||||
static const JSC::Intrinsic s_nodeEventsOnAsyncIteratorCodeIntrinsic = JSC::NoIntrinsic;
|
||||
const char* const s_nodeEventsOnAsyncIteratorCode =
|
||||
"(function (emitter, event, options) {\n" \
|
||||
" \"use strict\";\n" \
|
||||
"\n" \
|
||||
" var { AbortSignal, Symbol, Number, Error } = globalThis;\n" \
|
||||
"\n" \
|
||||
" function makeAbortError(msg, opts = void 0) {\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" \
|
||||
" return new AbortError(msg, opts);\n" \
|
||||
" }\n" \
|
||||
"\n" \
|
||||
" if (@isUndefinedOrNull(emitter)) @throwTypeError(\"emitter is required\");\n" \
|
||||
" //\n" \
|
||||
" if (!(@isObject(emitter) && @isCallable(emitter.emit) && @isCallable(emitter.on)))\n" \
|
||||
" @throwTypeError(\"emitter must be an EventEmitter\");\n" \
|
||||
"\n" \
|
||||
" if (@isUndefinedOrNull(options)) options = {};\n" \
|
||||
"\n" \
|
||||
" //\n" \
|
||||
" var signal = options.signal;\n" \
|
||||
" if (signal !== undefined && (!@isObject(signal) || !(signal instanceof AbortSignal)))\n" \
|
||||
" @throwTypeError(\"options.signal must be an AbortSignal\");\n" \
|
||||
"\n" \
|
||||
" if (signal?.aborted) {\n" \
|
||||
" //\n" \
|
||||
" throw makeAbortError(@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" \
|
||||
" var listeners = [];\n" \
|
||||
"\n" \
|
||||
" function abortListener() {\n" \
|
||||
" errorHandler(makeAbortError(@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]);\n" \
|
||||
" }\n" \
|
||||
"\n" \
|
||||
" function closeHandler() {\n" \
|
||||
" removeAllListeners();\n" \
|
||||
" finished = true;\n" \
|
||||
" while (!unconsumedPromises.isEmpty()) {\n" \
|
||||
" const promise = unconsumedPromises.shift();\n" \
|
||||
" promise.@resolve.@call(@undefined, [@undefined]);\n" \
|
||||
" }\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" \
|
||||
" \n" \
|
||||
" closeHandler();\n" \
|
||||
" }\n" \
|
||||
" \n" \
|
||||
" function addEventListener(emitter, event, handler) {\n" \
|
||||
" emitter.on(event, handler);\n" \
|
||||
" listeners.push([emitter, event, handler]);\n" \
|
||||
" }\n" \
|
||||
" \n" \
|
||||
" function removeAllListeners() {\n" \
|
||||
" while (listeners.length > 0) {\n" \
|
||||
" var entry = listeners.pop();\n" \
|
||||
" var [emitter, event, handler] = entry;\n" \
|
||||
" emitter.off(event, handler);\n" \
|
||||
" }\n" \
|
||||
" }\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([value]);\n" \
|
||||
" }\n" \
|
||||
"\n" \
|
||||
" //\n" \
|
||||
" //\n" \
|
||||
" //\n" \
|
||||
" if (error) {\n" \
|
||||
" throw error;\n" \
|
||||
" }\n" \
|
||||
"\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" \
|
||||
" 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) signal.addEventListener(\"abort\", abortListener, { once: true });\n" \
|
||||
"\n" \
|
||||
" var iterator = createIterator();\n" \
|
||||
" @Object.defineProperties(iterator, {\n" \
|
||||
" return: {\n" \
|
||||
" value: () => closeHandler(),\n" \
|
||||
" },\n" \
|
||||
" throw: {\n" \
|
||||
" value: (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" \
|
||||
" [Symbol.asyncIterator]: {\n" \
|
||||
" value: () => iterator,\n" \
|
||||
" },\n" \
|
||||
" });\n" \
|
||||
" return iterator;\n" \
|
||||
"})\n" \
|
||||
;
|
||||
|
||||
const JSC::ConstructAbility s_nodeEventsOncePromiseCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
|
||||
const JSC::ConstructorKind s_nodeEventsOncePromiseCodeConstructorKind = JSC::ConstructorKind::None;
|
||||
const JSC::ImplementationVisibility s_nodeEventsOncePromiseCodeImplementationVisibility = JSC::ImplementationVisibility::Public;
|
||||
const int s_nodeEventsOncePromiseCodeLength = 2418;
|
||||
static const JSC::Intrinsic s_nodeEventsOncePromiseCodeIntrinsic = JSC::NoIntrinsic;
|
||||
const char* const s_nodeEventsOncePromiseCode =
|
||||
"(function (emitter, name, options) {\n" \
|
||||
" \"use strict\";\n" \
|
||||
"\n" \
|
||||
" var { AbortSignal, Error } = globalThis;\n" \
|
||||
"\n" \
|
||||
" function makeAbortError(msg, opts = void 0) {\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" \
|
||||
" return new AbortError(msg, opts);\n" \
|
||||
" }\n" \
|
||||
"\n" \
|
||||
" if (@isUndefinedOrNull(emitter)) return @Promise.@reject(@makeTypeError(\"emitter is required\"));\n" \
|
||||
" //\n" \
|
||||
" if (!(@isObject(emitter) && @isCallable(emitter.emit) && @isCallable(emitter.on)))\n" \
|
||||
" return @Promise.@reject(@makeTypeError(\"emitter must be an EventEmitter\"));\n" \
|
||||
"\n" \
|
||||
" if (@isUndefinedOrNull(options)) options = {};\n" \
|
||||
"\n" \
|
||||
" //\n" \
|
||||
" var signal = options.signal;\n" \
|
||||
" if (signal !== @undefined && (!@isObject(signal) || !(signal instanceof AbortSignal)))\n" \
|
||||
" return @Promise.@reject(@makeTypeError(\"options.signal must be an AbortSignal\"));\n" \
|
||||
"\n" \
|
||||
" if (signal?.aborted) {\n" \
|
||||
" //\n" \
|
||||
" return @Promise.@reject(makeAbortError(@undefined, { cause: signal?.reason }));\n" \
|
||||
" }\n" \
|
||||
"\n" \
|
||||
" var eventPromiseCapability = @newPromiseCapability(@Promise);\n" \
|
||||
"\n" \
|
||||
" var errorListener = (err) => {\n" \
|
||||
" emitter.removeListener(name, resolver);\n" \
|
||||
" if (!@isUndefinedOrNull(signal)) {\n" \
|
||||
" signal.removeEventListener(\"abort\", abortListener);\n" \
|
||||
" }\n" \
|
||||
" eventPromiseCapability.@reject.@call(@undefined, err);\n" \
|
||||
" };\n" \
|
||||
"\n" \
|
||||
" var resolver = (...args) => {\n" \
|
||||
" if (@isCallable(emitter.removeListener)) {\n" \
|
||||
" emitter.removeListener(\"error\", errorListener);\n" \
|
||||
" }\n" \
|
||||
" if (!@isUndefinedOrNull(signal)) {\n" \
|
||||
" signal.removeEventListener(\"abort\", abortListener);\n" \
|
||||
" }\n" \
|
||||
" eventPromiseCapability.@resolve.@call(@undefined, args);\n" \
|
||||
" };\n" \
|
||||
" \n" \
|
||||
" emitter.once(name, resolver);\n" \
|
||||
" if (name !== \"error\" && @isCallable(emitter.once)) {\n" \
|
||||
" //\n" \
|
||||
" //\n" \
|
||||
" emitter.once(\"error\", errorListener);\n" \
|
||||
" }\n" \
|
||||
"\n" \
|
||||
" function abortListener() {\n" \
|
||||
" emitter.removeListener(name, resolver);\n" \
|
||||
" emitter.removeListener(\"error\", errorListener);\n" \
|
||||
" eventPromiseCapability.@reject.@call(@undefined, makeAbortError(@undefined, { cause: signal?.reason }));\n" \
|
||||
" }\n" \
|
||||
"\n" \
|
||||
" if (!@isUndefinedOrNull(signal))\n" \
|
||||
" signal.addEventListener(\"abort\", abortListener, { once: true });\n" \
|
||||
"\n" \
|
||||
" return eventPromiseCapability.@promise;\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
|
||||
137
src/bun.js/builtins/cpp/NodeEventsBuiltins.h
Normal file
137
src/bun.js/builtins/cpp/NodeEventsBuiltins.h
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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;
|
||||
extern const char* const s_nodeEventsOncePromiseCode;
|
||||
extern const int s_nodeEventsOncePromiseCodeLength;
|
||||
extern const JSC::ConstructAbility s_nodeEventsOncePromiseCodeConstructAbility;
|
||||
extern const JSC::ConstructorKind s_nodeEventsOncePromiseCodeConstructorKind;
|
||||
extern const JSC::ImplementationVisibility s_nodeEventsOncePromiseCodeImplementationVisibility;
|
||||
|
||||
#define WEBCORE_FOREACH_NODEEVENTS_BUILTIN_DATA(macro) \
|
||||
macro(onAsyncIterator, nodeEventsOnAsyncIterator, 3) \
|
||||
macro(oncePromise, nodeEventsOncePromise, 3) \
|
||||
|
||||
#define WEBCORE_BUILTIN_NODEEVENTS_ONASYNCITERATOR 1
|
||||
#define WEBCORE_BUILTIN_NODEEVENTS_ONCEPROMISE 1
|
||||
|
||||
#define WEBCORE_FOREACH_NODEEVENTS_BUILTIN_CODE(macro) \
|
||||
macro(nodeEventsOnAsyncIteratorCode, onAsyncIterator, ASCIILiteral(), s_nodeEventsOnAsyncIteratorCodeLength) \
|
||||
macro(nodeEventsOncePromiseCode, oncePromise, ASCIILiteral(), s_nodeEventsOncePromiseCodeLength) \
|
||||
|
||||
#define WEBCORE_FOREACH_NODEEVENTS_BUILTIN_FUNCTION_NAME(macro) \
|
||||
macro(onAsyncIterator) \
|
||||
macro(oncePromise) \
|
||||
|
||||
#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
|
||||
@@ -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;
|
||||
|
||||
@@ -112,4 +112,4 @@ function write(input) {
|
||||
|
||||
writer.flush(true);
|
||||
return wrote;
|
||||
}
|
||||
}
|
||||
|
||||
267
src/bun.js/builtins/js/NodeEvents.js
Normal file
267
src/bun.js/builtins/js/NodeEvents.js
Normal file
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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 { AbortSignal, Symbol, Number, Error } = globalThis;
|
||||
|
||||
function makeAbortError(msg, opts = void 0) {
|
||||
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";
|
||||
}
|
||||
};
|
||||
return new AbortError(msg, opts);
|
||||
}
|
||||
|
||||
if (@isUndefinedOrNull(emitter)) @throwTypeError("emitter is required");
|
||||
// TODO: Do a more accurate check
|
||||
if (!(@isObject(emitter) && @isCallable(emitter.emit) && @isCallable(emitter.on)))
|
||||
@throwTypeError("emitter must be an EventEmitter");
|
||||
|
||||
if (@isUndefinedOrNull(options)) options = {};
|
||||
|
||||
// Parameters validation
|
||||
var signal = options.signal;
|
||||
if (signal !== undefined && (!@isObject(signal) || !(signal instanceof AbortSignal)))
|
||||
@throwTypeError("options.signal must be an AbortSignal");
|
||||
|
||||
if (signal?.aborted) {
|
||||
// TODO: Make this a builtin
|
||||
throw makeAbortError(@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;
|
||||
var listeners = [];
|
||||
|
||||
function abortListener() {
|
||||
errorHandler(makeAbortError(@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]);
|
||||
}
|
||||
|
||||
function closeHandler() {
|
||||
removeAllListeners();
|
||||
finished = true;
|
||||
while (!unconsumedPromises.isEmpty()) {
|
||||
const promise = unconsumedPromises.shift();
|
||||
promise.@resolve.@call(@undefined, [@undefined]);
|
||||
}
|
||||
return @createFulfilledPromise([@undefined]);
|
||||
}
|
||||
|
||||
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);
|
||||
listeners.push([emitter, event, handler]);
|
||||
}
|
||||
|
||||
function removeAllListeners() {
|
||||
while (listeners.length > 0) {
|
||||
var entry = listeners.pop();
|
||||
var [emitter, event, handler] = entry;
|
||||
emitter.off(event, handler);
|
||||
}
|
||||
}
|
||||
|
||||
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([value]);
|
||||
}
|
||||
|
||||
// Check if error happened before yielding anything
|
||||
// This happens one time if at all, because after 'error'
|
||||
// we stop listening
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// If the iterator is finished, break
|
||||
if (finished) break;
|
||||
|
||||
// Wait until an event happens
|
||||
var nextEventPromiseCapability = @newPromiseCapability(@Promise);
|
||||
unconsumedPromises.push(nextEventPromiseCapability);
|
||||
yield nextEventPromiseCapability.@promise;
|
||||
}
|
||||
} finally {
|
||||
closeHandler();
|
||||
}
|
||||
};
|
||||
|
||||
// Adding event handlers
|
||||
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.addEventListener("abort", abortListener, { once: true });
|
||||
|
||||
var iterator = createIterator();
|
||||
@Object.defineProperties(iterator, {
|
||||
return: {
|
||||
value: () => closeHandler(),
|
||||
},
|
||||
throw: {
|
||||
value: (err) => {
|
||||
if (!err || !(err instanceof Error)) {
|
||||
throw new TypeError("EventEmitter.AsyncIterator must be called with an error");
|
||||
}
|
||||
errorHandler(err);
|
||||
},
|
||||
},
|
||||
[Symbol.asyncIterator]: {
|
||||
value: () => iterator,
|
||||
},
|
||||
});
|
||||
return iterator;
|
||||
}
|
||||
|
||||
function oncePromise(emitter, name, options) {
|
||||
"use strict";
|
||||
|
||||
var { AbortSignal, Error } = globalThis;
|
||||
|
||||
function makeAbortError(msg, opts = void 0) {
|
||||
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";
|
||||
}
|
||||
};
|
||||
return new AbortError(msg, opts);
|
||||
}
|
||||
|
||||
if (@isUndefinedOrNull(emitter)) return @Promise.@reject(@makeTypeError("emitter is required"));
|
||||
// TODO: Do a more accurate check
|
||||
if (!(@isObject(emitter) && @isCallable(emitter.emit) && @isCallable(emitter.on)))
|
||||
return @Promise.@reject(@makeTypeError("emitter must be an EventEmitter"));
|
||||
|
||||
if (@isUndefinedOrNull(options)) options = {};
|
||||
|
||||
// Parameters validation
|
||||
var signal = options.signal;
|
||||
if (signal !== @undefined && (!@isObject(signal) || !(signal instanceof AbortSignal)))
|
||||
return @Promise.@reject(@makeTypeError("options.signal must be an AbortSignal"));
|
||||
|
||||
if (signal?.aborted) {
|
||||
// TODO: Make this a builtin
|
||||
return @Promise.@reject(makeAbortError(@undefined, { cause: signal?.reason }));
|
||||
}
|
||||
|
||||
var eventPromiseCapability = @newPromiseCapability(@Promise);
|
||||
|
||||
var errorListener = (err) => {
|
||||
emitter.removeListener(name, resolver);
|
||||
if (!@isUndefinedOrNull(signal)) {
|
||||
signal.removeEventListener("abort", abortListener);
|
||||
}
|
||||
eventPromiseCapability.@reject.@call(@undefined, err);
|
||||
};
|
||||
|
||||
var resolver = (...args) => {
|
||||
if (@isCallable(emitter.removeListener)) {
|
||||
emitter.removeListener("error", errorListener);
|
||||
}
|
||||
if (!@isUndefinedOrNull(signal)) {
|
||||
signal.removeEventListener("abort", abortListener);
|
||||
}
|
||||
eventPromiseCapability.@resolve.@call(@undefined, args);
|
||||
};
|
||||
|
||||
emitter.once(name, resolver);
|
||||
if (name !== "error" && @isCallable(emitter.once)) {
|
||||
// EventTarget does not have `error` event semantics like Node
|
||||
// EventEmitters, we listen to `error` events only on EventEmitters.
|
||||
emitter.once("error", errorListener);
|
||||
}
|
||||
|
||||
function abortListener() {
|
||||
emitter.removeListener(name, resolver);
|
||||
emitter.removeListener("error", errorListener);
|
||||
eventPromiseCapability.@reject.@call(@undefined, makeAbortError(@undefined, { cause: signal?.reason }));
|
||||
}
|
||||
|
||||
if (!@isUndefinedOrNull(signal))
|
||||
signal.addEventListener("abort", abortListener, { once: true });
|
||||
|
||||
return eventPromiseCapability.@promise;
|
||||
}
|
||||
@@ -2206,4 +2206,4 @@ function readableStreamDefineLazyIterators(prototype) {
|
||||
@Object.@defineProperty(prototype, asyncIterator, { value: createAsyncIterator });
|
||||
@Object.@defineProperty(prototype, "values", { value: createValues });
|
||||
return prototype;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,14 +24,6 @@ inline void generateEventsSourceCode(JSC::JSGlobalObject *lexicalGlobalObject,
|
||||
exportValues.append(JSC::JSFunction::create(
|
||||
vm, lexicalGlobalObject, 0, MAKE_STATIC_STRING_IMPL("listenerCount"),
|
||||
Events_functionListenerCount, ImplementationVisibility::Public));
|
||||
exportNames.append(JSC::Identifier::fromString(vm, "once"_s));
|
||||
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 +33,30 @@ 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);
|
||||
|
||||
exportNames.append(JSC::Identifier::fromString(vm, "once"_s));
|
||||
auto *oncePromiseFnPtr = eventEmitterModuleCJS->putDirectBuiltinFunction(
|
||||
vm, globalObject, JSC::Identifier::fromString(vm, "once"_s),
|
||||
nodeEventsOncePromiseCodeGenerator(vm),
|
||||
PropertyAttribute::Builtin | PropertyAttribute::DontDelete);
|
||||
exportValues.append(oncePromiseFnPtr);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
var firstEvents = myEmitter._events;
|
||||
expect(myEmitter.listenerCount("event")).toBe(1);
|
||||
|
||||
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);
|
||||
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);
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
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();
|
||||
|
||||
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");
|
||||
})();
|
||||
|
||||
await expectMaxObjectTypeCount(expect, "EventEmitter", startCount);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
719
test/js/node/events/node-events.node.test.ts
Normal file
719
test/js/node/events/node-events.node.test.ts
Normal file
@@ -0,0 +1,719 @@
|
||||
import { EventEmitter, getEventListeners, on, once } from "node:events";
|
||||
import { createTest } from "node-harness";
|
||||
|
||||
const { beforeAll, expect, assert, strictEqual, describe, it, createCallCheckCtx, createDoneDotAll } = createTest(
|
||||
import.meta.path,
|
||||
);
|
||||
// const NodeEventTarget = globalThis.EventTarget;
|
||||
|
||||
describe("node:events.on() (EventEmitter AsyncIterator)", () => {
|
||||
it("should return an async iterator", async () => {
|
||||
const ee = new EventEmitter();
|
||||
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) {
|
||||
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);
|
||||
});
|
||||
|
||||
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");
|
||||
|
||||
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) {
|
||||
looped = true;
|
||||
}
|
||||
} catch (err) {
|
||||
thrown = true;
|
||||
assert.strictEqual(err, _err);
|
||||
}
|
||||
assert.strictEqual(thrown, true);
|
||||
assert.strictEqual(looped, false);
|
||||
});
|
||||
|
||||
it("should throw when error emitted after successful events", async () => {
|
||||
const ee = new EventEmitter();
|
||||
const _err = new Error("kaboom");
|
||||
const iterable = on(ee, "foo");
|
||||
|
||||
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) {
|
||||
const _expected = expected.shift();
|
||||
if (_expected !== undefined) current.push(_expected);
|
||||
received.push(event);
|
||||
}
|
||||
} catch (err) {
|
||||
thrownErr = err;
|
||||
}
|
||||
|
||||
assert.deepStrictEqual(current, received);
|
||||
assert.strictEqual(ee.listenerCount("foo"), 0);
|
||||
assert.strictEqual(ee.listenerCount("error"), 0);
|
||||
|
||||
expect(thrownErr).toBeInstanceOf(Error);
|
||||
assert.strictEqual(thrownErr, _err);
|
||||
});
|
||||
|
||||
it("should throw when error thrown from inside loop", async () => {
|
||||
const ee = new EventEmitter();
|
||||
const _err = new Error("kaboom");
|
||||
|
||||
const iterable = on(ee, "foo");
|
||||
|
||||
ee.emit("foo", 42);
|
||||
|
||||
let looped = false;
|
||||
let thrown = false;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
assert.strictEqual(thrown, true);
|
||||
assert.strictEqual(looped, true);
|
||||
assert.strictEqual(ee.listenerCount("foo"), 0);
|
||||
assert.strictEqual(ee.listenerCount("error"), 0);
|
||||
});
|
||||
|
||||
it("should allow for async iteration via .next()", async done => {
|
||||
const ee = new EventEmitter();
|
||||
const iterable = on(ee, "foo");
|
||||
|
||||
process.nextTick(() => {
|
||||
ee.emit("foo", "bar");
|
||||
ee.emit("foo", 42);
|
||||
// @ts-ignore
|
||||
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,
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
describe(".throw()", () => {
|
||||
let ee: EventEmitter;
|
||||
let iterable: AsyncIterableIterator<any>;
|
||||
|
||||
beforeAll(() => {
|
||||
ee = new EventEmitter();
|
||||
iterable = on(ee, "foo");
|
||||
});
|
||||
|
||||
it("should throw a `TypeError` when calling without args", async () => {
|
||||
expect(() => {
|
||||
iterable.throw!();
|
||||
}).toThrow(TypeError);
|
||||
|
||||
assert.strictEqual(ee.listenerCount("foo"), 1);
|
||||
assert.strictEqual(ee.listenerCount("error"), 1);
|
||||
});
|
||||
|
||||
it("should throw when called with an error", async () => {
|
||||
const _err = new Error("kaboom");
|
||||
|
||||
ee.emit("foo", "bar");
|
||||
ee.emit("foo", 42);
|
||||
iterable.throw!(_err);
|
||||
|
||||
const expected = [["bar"], [42]];
|
||||
|
||||
let thrown = false;
|
||||
let looped = false;
|
||||
|
||||
try {
|
||||
for await (const event of iterable) {
|
||||
assert.deepStrictEqual(event, expected.shift());
|
||||
looped = true;
|
||||
}
|
||||
} catch (err) {
|
||||
thrown = true;
|
||||
assert.strictEqual(err, _err);
|
||||
}
|
||||
|
||||
assert.strictEqual(looped, true);
|
||||
assert.strictEqual(thrown, true);
|
||||
assert.strictEqual(ee.listenerCount("foo"), 0);
|
||||
assert.strictEqual(ee.listenerCount("error"), 0);
|
||||
});
|
||||
});
|
||||
|
||||
it("should add an error listener when the iterable is created", () => {
|
||||
const ee = new EventEmitter();
|
||||
on(ee, "foo");
|
||||
assert.strictEqual(ee.listenerCount("error"), 1);
|
||||
});
|
||||
|
||||
it("should throw when called with an aborted signal", () => {
|
||||
const ee = new EventEmitter();
|
||||
const abortedSignal = AbortSignal.abort();
|
||||
[1, {}, null, false, "hi"].forEach((signal: any) => {
|
||||
assert.throws(() => on(ee, "foo", { signal }), Error);
|
||||
});
|
||||
assert.throws(() => on(ee, "foo", { signal: abortedSignal }), {
|
||||
name: "AbortError",
|
||||
});
|
||||
});
|
||||
|
||||
it("should NOT THROW an `AbortError` AFTER done iterating over events", async _done => {
|
||||
let _doneCalled = false;
|
||||
const done = (err?: Error) => {
|
||||
if (_doneCalled) return;
|
||||
_doneCalled = true;
|
||||
_done(err);
|
||||
};
|
||||
|
||||
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()
|
||||
.catch(err => done(err))
|
||||
.finally(() => {
|
||||
clearInterval(i);
|
||||
if (!_doneCalled) expect(true).toBe(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should THROW an `AbortError` BEFORE done iterating over events", async _done => {
|
||||
let _doneCalled = false;
|
||||
const done = (err?: Error) => {
|
||||
if (_doneCalled) return;
|
||||
_doneCalled = true;
|
||||
_done(err);
|
||||
};
|
||||
|
||||
let count = 0;
|
||||
|
||||
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);
|
||||
|
||||
setTimeout(() => ac.abort(), 50);
|
||||
|
||||
async function foo() {
|
||||
for await (const f of on(ee, "foo", { signal: ac.signal })) {
|
||||
assert.deepStrictEqual(f, ["foo"]);
|
||||
}
|
||||
}
|
||||
|
||||
foo()
|
||||
.then(() => done(new Error("Should not be called")))
|
||||
.catch(
|
||||
mustCall(error => {
|
||||
assert.strictEqual(error.name, "AbortError");
|
||||
}),
|
||||
)
|
||||
.finally(() => {
|
||||
clearInterval(i);
|
||||
closeTimers();
|
||||
if (!_doneCalled) finalDone();
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Uncomment tests for NodeEventTarget and Web EventTarget
|
||||
|
||||
// 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 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 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",
|
||||
// });
|
||||
// }
|
||||
|
||||
// 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);
|
||||
// });
|
||||
// }
|
||||
});
|
||||
|
||||
describe("node:events.once()", () => {
|
||||
it("should resolve with the first event", async () => {
|
||||
const ee = new EventEmitter();
|
||||
|
||||
setImmediate(() => {
|
||||
ee.emit("myevent", 42);
|
||||
});
|
||||
|
||||
const [value] = await once(ee, "myevent");
|
||||
assert.strictEqual(value, 42);
|
||||
assert.strictEqual(ee.listenerCount("error"), 0);
|
||||
assert.strictEqual(ee.listenerCount("myevent"), 0);
|
||||
});
|
||||
|
||||
it("should allow passing `null` for `options` arg", async () => {
|
||||
const ee = new EventEmitter();
|
||||
|
||||
setImmediate(() => {
|
||||
ee.emit("myevent", 42);
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
const [value] = await once(ee, "myevent", null);
|
||||
assert.strictEqual(value, 42);
|
||||
});
|
||||
|
||||
it("should return two args when two args are emitted", async () => {
|
||||
const ee = new EventEmitter();
|
||||
|
||||
setImmediate(() => {
|
||||
ee.emit("myevent", 42, 24);
|
||||
});
|
||||
|
||||
const value = await once(ee, "myevent");
|
||||
assert.deepStrictEqual(value, [42, 24]);
|
||||
});
|
||||
|
||||
it("should throw an error when an error is emitted", async () => {
|
||||
const ee = new EventEmitter();
|
||||
|
||||
const expected = new Error("kaboom");
|
||||
setImmediate(() => {
|
||||
ee.emit("error", expected);
|
||||
});
|
||||
|
||||
let err;
|
||||
try {
|
||||
await once(ee, "myevent");
|
||||
} catch (_e) {
|
||||
err = _e;
|
||||
}
|
||||
|
||||
assert.strictEqual(err, expected);
|
||||
assert.strictEqual(ee.listenerCount("error"), 0);
|
||||
assert.strictEqual(ee.listenerCount("myevent"), 0);
|
||||
});
|
||||
|
||||
it("should throw an error when an error is emitted when `AbortSignal` is attached", async () => {
|
||||
const ee = new EventEmitter();
|
||||
const ac = new AbortController();
|
||||
const signal = ac.signal;
|
||||
|
||||
const expected = new Error("boom");
|
||||
let err;
|
||||
setImmediate(() => {
|
||||
ee.emit("error", expected);
|
||||
});
|
||||
|
||||
const promise = once(ee, "myevent", { signal });
|
||||
strictEqual(ee.listenerCount("error"), 1);
|
||||
|
||||
// TODO: Uncomment when getEventListeners is working properly
|
||||
// strictEqual(getEventListeners(signal, "abort").length, 1);
|
||||
|
||||
try {
|
||||
await promise;
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
strictEqual(err, expected);
|
||||
strictEqual(ee.listenerCount("error"), 0);
|
||||
strictEqual(ee.listenerCount("myevent"), 0);
|
||||
// strictEqual(getEventListeners(signal, "abort").length, 0);
|
||||
});
|
||||
|
||||
it("should stop listening if we throw an error", async () => {
|
||||
const ee = new EventEmitter();
|
||||
|
||||
const expected = new Error("kaboom");
|
||||
let err;
|
||||
|
||||
setImmediate(() => {
|
||||
ee.emit("error", expected);
|
||||
ee.emit("myevent", 42, 24);
|
||||
});
|
||||
|
||||
try {
|
||||
await once(ee, "myevent");
|
||||
} catch (_e) {
|
||||
err = _e;
|
||||
}
|
||||
|
||||
strictEqual(err, expected);
|
||||
strictEqual(ee.listenerCount("error"), 0);
|
||||
strictEqual(ee.listenerCount("myevent"), 0);
|
||||
});
|
||||
|
||||
it("should return error instead of throwing if event is error", async () => {
|
||||
const ee = new EventEmitter();
|
||||
|
||||
const expected = new Error("kaboom");
|
||||
setImmediate(() => {
|
||||
ee.emit("error", expected);
|
||||
});
|
||||
|
||||
const promise = once(ee, "error");
|
||||
strictEqual(ee.listenerCount("error"), 1);
|
||||
const [err] = await promise;
|
||||
strictEqual(err, expected);
|
||||
strictEqual(ee.listenerCount("error"), 0);
|
||||
strictEqual(ee.listenerCount("myevent"), 0);
|
||||
});
|
||||
|
||||
it("should throw on invalid signal option", async done => {
|
||||
const ee = new EventEmitter();
|
||||
ee.on("error", err => {
|
||||
done(new Error("should not be called", { cause: err }));
|
||||
});
|
||||
let iters = 0;
|
||||
for (const signal of [1, {}, "hi", null, false]) {
|
||||
let threw = false;
|
||||
try {
|
||||
await once(ee, "foo", { signal });
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
expect(e).toBeInstanceOf(TypeError);
|
||||
}
|
||||
expect(threw).toBe(true);
|
||||
iters++;
|
||||
}
|
||||
expect(iters).toBe(5);
|
||||
done();
|
||||
});
|
||||
|
||||
it("should throw `AbortError` when signal is already aborted", async done => {
|
||||
const ee = new EventEmitter();
|
||||
ee.on("error", err => done(new Error("should not be called", { cause: err })));
|
||||
const abortedSignal = AbortSignal.abort();
|
||||
|
||||
expect(() => on(ee, "foo", { signal: abortedSignal })).toThrow(/aborted/);
|
||||
|
||||
// let threw = false;
|
||||
// try {
|
||||
// await once(ee, "foo", { signal: abortedSignal });
|
||||
// } catch (e) {
|
||||
// threw = true;
|
||||
// expect(e).toBeInstanceOf(Error);
|
||||
// expect((e as Error).name).toBe("AbortError");
|
||||
// }
|
||||
|
||||
// expect(threw).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it("should throw `AbortError` when signal is aborted before event is emitted", async done => {
|
||||
const ee = new EventEmitter();
|
||||
ee.on("error", err => done(new Error("should not be called", { cause: err })));
|
||||
const ac = new AbortController();
|
||||
const signal = ac.signal;
|
||||
|
||||
const promise = once(ee, "foo", { signal });
|
||||
ac.abort();
|
||||
|
||||
let threw = false;
|
||||
try {
|
||||
await promise;
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
expect(e).toBeInstanceOf(Error);
|
||||
expect((e as Error).name).toBe("AbortError");
|
||||
}
|
||||
|
||||
expect(threw).toBe(true);
|
||||
done();
|
||||
});
|
||||
|
||||
it("should not throw `AbortError` when signal is aborted after event is emitted", async () => {
|
||||
const ee = new EventEmitter();
|
||||
const ac = new AbortController();
|
||||
const signal = ac.signal;
|
||||
|
||||
setImmediate(() => {
|
||||
ee.emit("foo");
|
||||
ac.abort();
|
||||
});
|
||||
|
||||
const promise = once(ee, "foo", { signal });
|
||||
// TODO: Uncomment when getEventListeners is working properly
|
||||
// strictEqual(getEventListeners(signal, "abort").length, 1);
|
||||
|
||||
await promise;
|
||||
expect(true).toBeTruthy();
|
||||
// strictEqual(getEventListeners(signal, "abort").length, 0);
|
||||
});
|
||||
|
||||
it("should remove listeners when signal is aborted", async () => {
|
||||
const ee = new EventEmitter();
|
||||
const ac = new AbortController();
|
||||
|
||||
const promise = once(ee, "foo", { signal: ac.signal });
|
||||
strictEqual(ee.listenerCount("foo"), 1);
|
||||
strictEqual(ee.listenerCount("error"), 1);
|
||||
|
||||
setImmediate(() => {
|
||||
ac.abort();
|
||||
});
|
||||
|
||||
try {
|
||||
await promise;
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(Error);
|
||||
expect((e as Error).name).toBe("AbortError");
|
||||
|
||||
strictEqual(ee.listenerCount("foo"), 0);
|
||||
strictEqual(ee.listenerCount("error"), 0);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Uncomment event target tests once we have EventTarget support for once()
|
||||
|
||||
// async function onceWithEventTarget() {
|
||||
// const et = new EventTarget();
|
||||
// const event = new Event("myevent");
|
||||
// process.nextTick(() => {
|
||||
// et.dispatchEvent(event);
|
||||
// });
|
||||
// const [value] = await once(et, "myevent");
|
||||
// strictEqual(value, event);
|
||||
// }
|
||||
|
||||
// async function onceWithEventTargetError() {
|
||||
// const et = new EventTarget();
|
||||
// const error = new Event("error");
|
||||
// process.nextTick(() => {
|
||||
// et.dispatchEvent(error);
|
||||
// });
|
||||
|
||||
// const [err] = await once(et, "error");
|
||||
// strictEqual(err, error);
|
||||
// }
|
||||
|
||||
// async function eventTargetAbortSignalBefore() {
|
||||
// const et = new EventTarget();
|
||||
// const abortedSignal = AbortSignal.abort();
|
||||
|
||||
// await Promise.all(
|
||||
// [1, {}, "hi", null, false].map(signal => {
|
||||
// return rejects(once(et, "foo", { signal }), {
|
||||
// code: "ERR_INVALID_ARG_TYPE",
|
||||
// });
|
||||
// }),
|
||||
// );
|
||||
|
||||
// return rejects(once(et, "foo", { signal: abortedSignal }), {
|
||||
// name: "AbortError",
|
||||
// });
|
||||
// }
|
||||
|
||||
// async function eventTargetAbortSignalAfter() {
|
||||
// const et = new EventTarget();
|
||||
// const ac = new AbortController();
|
||||
// const r = rejects(once(et, "foo", { signal: ac.signal }), {
|
||||
// name: "AbortError",
|
||||
// });
|
||||
// process.nextTick(() => ac.abort());
|
||||
// return r;
|
||||
// }
|
||||
|
||||
// async function eventTargetAbortSignalAfterEvent() {
|
||||
// const et = new EventTarget();
|
||||
// const ac = new AbortController();
|
||||
// process.nextTick(() => {
|
||||
// et.dispatchEvent(new Event("foo"));
|
||||
// ac.abort();
|
||||
// });
|
||||
// await once(et, "foo", { signal: ac.signal });
|
||||
// }
|
||||
});
|
||||
Reference in New Issue
Block a user