mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
feat: Event emitter node polyfill (#3944)
This commit is contained in:
parent
e1105a1594
commit
81905a867e
6 changed files with 814 additions and 23 deletions
|
@ -16,7 +16,7 @@ deno standard library as it's a compatiblity module.
|
|||
- [ ] crypto
|
||||
- [ ] dgram
|
||||
- [ ] dns
|
||||
- [ ] events
|
||||
- [x] events
|
||||
- [x] fs _partly_
|
||||
- [ ] http
|
||||
- [ ] http2
|
||||
|
|
383
std/node/events.ts
Normal file
383
std/node/events.ts
Normal file
|
@ -0,0 +1,383 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
// Copyright (c) 2019 Denolibs authors. All rights reserved. MIT license.
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import { validateIntegerRange } from "./util.ts";
|
||||
|
||||
export interface WrappedFunction extends Function {
|
||||
listener: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* See also https://nodejs.org/api/events.html
|
||||
*/
|
||||
export default class EventEmitter {
|
||||
public static defaultMaxListeners = 10;
|
||||
private maxListeners: number | undefined;
|
||||
private _events: Map<string | symbol, Array<Function | WrappedFunction>>;
|
||||
|
||||
public constructor() {
|
||||
this._events = new Map();
|
||||
}
|
||||
|
||||
private _addListener(
|
||||
eventName: string | symbol,
|
||||
listener: Function | WrappedFunction,
|
||||
prepend: boolean
|
||||
): this {
|
||||
this.emit("newListener", eventName, listener);
|
||||
if (this._events.has(eventName)) {
|
||||
const listeners = this._events.get(eventName) as Array<
|
||||
Function | WrappedFunction
|
||||
>;
|
||||
if (prepend) {
|
||||
listeners.unshift(listener);
|
||||
} else {
|
||||
listeners.push(listener);
|
||||
}
|
||||
} else {
|
||||
this._events.set(eventName, [listener]);
|
||||
}
|
||||
const max = this.getMaxListeners();
|
||||
if (max > 0 && this.listenerCount(eventName) > max) {
|
||||
const warning = new Error(
|
||||
`Possible EventEmitter memory leak detected.
|
||||
${this.listenerCount(eventName)} ${eventName.toString()} listeners.
|
||||
Use emitter.setMaxListeners() to increase limit`
|
||||
);
|
||||
warning.name = "MaxListenersExceededWarning";
|
||||
console.warn(warning);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Alias for emitter.on(eventName, listener). */
|
||||
public addListener(
|
||||
eventName: string | symbol,
|
||||
listener: Function | WrappedFunction
|
||||
): this {
|
||||
return this._addListener(eventName, listener, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously calls each of the listeners registered for the event named
|
||||
* eventName, in the order they were registered, passing the supplied
|
||||
* arguments to each.
|
||||
* @return true if the event had listeners, false otherwise
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public emit(eventName: string | symbol, ...args: any[]): boolean {
|
||||
if (this._events.has(eventName)) {
|
||||
const listeners = (this._events.get(eventName) as Function[]).slice(); // We copy with slice() so array is not mutated during emit
|
||||
for (const listener of listeners) {
|
||||
try {
|
||||
listener.apply(this, args);
|
||||
} catch (err) {
|
||||
this.emit("error", err);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (eventName === "error") {
|
||||
const errMsg = args.length > 0 ? args[0] : Error("Unhandled error.");
|
||||
throw errMsg;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array listing the events for which the emitter has
|
||||
* registered listeners.
|
||||
*/
|
||||
public eventNames(): [string | symbol] {
|
||||
return Array.from(this._events.keys()) as [string | symbol];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current max listener value for the EventEmitter which is
|
||||
* either set by emitter.setMaxListeners(n) or defaults to
|
||||
* EventEmitter.defaultMaxListeners.
|
||||
*/
|
||||
public getMaxListeners(): number {
|
||||
return this.maxListeners || EventEmitter.defaultMaxListeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of listeners listening to the event named
|
||||
* eventName.
|
||||
*/
|
||||
public listenerCount(eventName: string | symbol): number {
|
||||
if (this._events.has(eventName)) {
|
||||
return (this._events.get(eventName) as Function[]).length;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private _listeners(
|
||||
target: EventEmitter,
|
||||
eventName: string | symbol,
|
||||
unwrap: boolean
|
||||
): Function[] {
|
||||
if (!target._events.has(eventName)) {
|
||||
return [];
|
||||
}
|
||||
const eventListeners: Function[] = target._events.get(
|
||||
eventName
|
||||
) as Function[];
|
||||
|
||||
return unwrap
|
||||
? this.unwrapListeners(eventListeners)
|
||||
: eventListeners.slice(0);
|
||||
}
|
||||
|
||||
private unwrapListeners(arr: Function[]): Function[] {
|
||||
const unwrappedListeners: Function[] = new Array(arr.length) as Function[];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
unwrappedListeners[i] = arr[i]["listener"] || arr[i];
|
||||
}
|
||||
return unwrappedListeners;
|
||||
}
|
||||
|
||||
/** Returns a copy of the array of listeners for the event named eventName.*/
|
||||
public listeners(eventName: string | symbol): Function[] {
|
||||
return this._listeners(this, eventName, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the array of listeners for the event named eventName,
|
||||
* including any wrappers (such as those created by .once()).
|
||||
*/
|
||||
public rawListeners(
|
||||
eventName: string | symbol
|
||||
): Array<Function | WrappedFunction> {
|
||||
return this._listeners(this, eventName, false);
|
||||
}
|
||||
|
||||
/** Alias for emitter.removeListener(). */
|
||||
public off(eventName: string | symbol, listener: Function): this {
|
||||
return this.removeListener(eventName, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the listener function to the end of the listeners array for the event
|
||||
* named eventName. No checks are made to see if the listener has already
|
||||
* been added. Multiple calls passing the same combination of eventName and
|
||||
* listener will result in the listener being added, and called, multiple
|
||||
* times.
|
||||
*/
|
||||
public on(
|
||||
eventName: string | symbol,
|
||||
listener: Function | WrappedFunction
|
||||
): this {
|
||||
return this.addListener(eventName, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a one-time listener function for the event named eventName. The next
|
||||
* time eventName is triggered, this listener is removed and then invoked.
|
||||
*/
|
||||
public once(eventName: string | symbol, listener: Function): this {
|
||||
const wrapped: WrappedFunction = this.onceWrap(eventName, listener);
|
||||
this.on(eventName, wrapped);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Wrapped function that calls EventEmitter.removeListener(eventName, self) on execution.
|
||||
private onceWrap(
|
||||
eventName: string | symbol,
|
||||
listener: Function
|
||||
): WrappedFunction {
|
||||
const wrapper = function(
|
||||
this: {
|
||||
eventName: string | symbol;
|
||||
listener: Function;
|
||||
rawListener: Function;
|
||||
context: EventEmitter;
|
||||
},
|
||||
...args: any[] // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
): void {
|
||||
this.context.removeListener(this.eventName, this.rawListener);
|
||||
this.listener.apply(this.context, args);
|
||||
};
|
||||
const wrapperContext = {
|
||||
eventName: eventName,
|
||||
listener: listener,
|
||||
rawListener: wrapper,
|
||||
context: this
|
||||
};
|
||||
const wrapped = wrapper.bind(wrapperContext);
|
||||
wrapperContext.rawListener = wrapped;
|
||||
wrapped.listener = listener;
|
||||
return wrapped as WrappedFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the listener function to the beginning of the listeners array for the
|
||||
* event named eventName. No checks are made to see if the listener has
|
||||
* already been added. Multiple calls passing the same combination of
|
||||
* eventName and listener will result in the listener being added, and
|
||||
* called, multiple times.
|
||||
*/
|
||||
public prependListener(
|
||||
eventName: string | symbol,
|
||||
listener: Function | WrappedFunction
|
||||
): this {
|
||||
return this._addListener(eventName, listener, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a one-time listener function for the event named eventName to the
|
||||
* beginning of the listeners array. The next time eventName is triggered,
|
||||
* this listener is removed, and then invoked.
|
||||
*/
|
||||
public prependOnceListener(
|
||||
eventName: string | symbol,
|
||||
listener: Function
|
||||
): this {
|
||||
const wrapped: WrappedFunction = this.onceWrap(eventName, listener);
|
||||
this.prependListener(eventName, wrapped);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Removes all listeners, or those of the specified eventName. */
|
||||
public removeAllListeners(eventName?: string | symbol): this {
|
||||
if (this._events === undefined) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (this._events.has(eventName)) {
|
||||
const listeners = (this._events.get(eventName) as Array<
|
||||
Function | WrappedFunction
|
||||
>).slice(); // Create a copy; We use it AFTER it's deleted.
|
||||
this._events.delete(eventName);
|
||||
for (const listener of listeners) {
|
||||
this.emit("removeListener", eventName, listener);
|
||||
}
|
||||
} else {
|
||||
const eventList: [string | symbol] = this.eventNames();
|
||||
eventList.map((value: string | symbol) => {
|
||||
this.removeAllListeners(value);
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified listener from the listener array for the event
|
||||
* named eventName.
|
||||
*/
|
||||
public removeListener(eventName: string | symbol, listener: Function): this {
|
||||
if (this._events.has(eventName)) {
|
||||
const arr: Array<Function | WrappedFunction> = this._events.get(
|
||||
eventName
|
||||
);
|
||||
|
||||
let listenerIndex = -1;
|
||||
for (let i = arr.length - 1; i >= 0; i--) {
|
||||
// arr[i]["listener"] is the reference to the listener inside a bound 'once' wrapper
|
||||
if (arr[i] == listener || arr[i]["listener"] == listener) {
|
||||
listenerIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (listenerIndex >= 0) {
|
||||
arr.splice(listenerIndex, 1);
|
||||
this.emit("removeListener", eventName, listener);
|
||||
if (arr.length === 0) {
|
||||
this._events.delete(eventName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* By default EventEmitters will print a warning if more than 10 listeners
|
||||
* are added for a particular event. This is a useful default that helps
|
||||
* finding memory leaks. Obviously, not all events should be limited to just
|
||||
* 10 listeners. The emitter.setMaxListeners() method allows the limit to be
|
||||
* modified for this specific EventEmitter instance. The value can be set to
|
||||
* Infinity (or 0) to indicate an unlimited number of listeners.
|
||||
*/
|
||||
public setMaxListeners(n: number): this {
|
||||
validateIntegerRange(n, "maxListeners", 0);
|
||||
this.maxListeners = n;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Promise that is fulfilled when the EventEmitter emits the given
|
||||
* event or that is rejected when the EventEmitter emits 'error'. The Promise
|
||||
* will resolve with an array of all the arguments emitted to the given event.
|
||||
*/
|
||||
export function once(
|
||||
emitter: EventEmitter | EventTarget,
|
||||
name: string
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): Promise<any[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (emitter instanceof EventTarget) {
|
||||
// EventTarget does not have `error` event semantics like Node
|
||||
// EventEmitters, we do not listen to `error` events here.
|
||||
emitter.addEventListener(
|
||||
name,
|
||||
(...args) => {
|
||||
resolve(args);
|
||||
},
|
||||
{ once: true, passive: false, capture: false }
|
||||
);
|
||||
return;
|
||||
} else if (emitter instanceof EventEmitter) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const eventListener = (...args: any[]): void => {
|
||||
if (errorListener !== undefined) {
|
||||
emitter.removeListener("error", errorListener);
|
||||
}
|
||||
resolve(args);
|
||||
};
|
||||
let errorListener: Function;
|
||||
|
||||
// Adding an error listener is not optional because
|
||||
// if an error is thrown on an event emitter we cannot
|
||||
// guarantee that the actual event we are waiting will
|
||||
// be fired. The result could be a silent way to create
|
||||
// memory or file descriptor leaks, which is something
|
||||
// we should avoid.
|
||||
if (name !== "error") {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
errorListener = (err: any): void => {
|
||||
emitter.removeListener(name, eventListener);
|
||||
reject(err);
|
||||
};
|
||||
|
||||
emitter.once("error", errorListener);
|
||||
}
|
||||
|
||||
emitter.once(name, eventListener);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
403
std/node/events_test.ts
Normal file
403
std/node/events_test.ts
Normal file
|
@ -0,0 +1,403 @@
|
|||
import { test } from "../testing/mod.ts";
|
||||
import {
|
||||
assert,
|
||||
assertEquals,
|
||||
fail,
|
||||
assertThrows
|
||||
} from "../testing/asserts.ts";
|
||||
import EventEmitter, { WrappedFunction, once } from "./events.ts";
|
||||
|
||||
const shouldNeverBeEmitted: Function = () => {
|
||||
fail("Should never be called");
|
||||
};
|
||||
|
||||
test({
|
||||
name:
|
||||
'When adding a new event, "eventListener" event is fired before adding the listener',
|
||||
fn() {
|
||||
let eventsFired = [];
|
||||
const testEmitter = new EventEmitter();
|
||||
testEmitter.on("newListener", event => {
|
||||
if (event !== "newListener") {
|
||||
eventsFired.push("newListener");
|
||||
}
|
||||
});
|
||||
testEmitter.on("event", () => {
|
||||
eventsFired.push("event");
|
||||
});
|
||||
assertEquals(eventsFired, ["newListener"]);
|
||||
eventsFired = [];
|
||||
testEmitter.emit("event");
|
||||
assertEquals(eventsFired, ["event"]);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name:
|
||||
'When removing a listenert, "removeListener" event is fired after removal',
|
||||
fn() {
|
||||
const eventsFired = [];
|
||||
const testEmitter = new EventEmitter();
|
||||
testEmitter.on("removeListener", () => {
|
||||
eventsFired.push("removeListener");
|
||||
});
|
||||
const eventFunction = function(): void {
|
||||
eventsFired.push("event");
|
||||
};
|
||||
testEmitter.on("event", eventFunction);
|
||||
|
||||
assertEquals(eventsFired, []);
|
||||
testEmitter.removeListener("event", eventFunction);
|
||||
assertEquals(eventsFired, ["removeListener"]);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name:
|
||||
"Default max listeners is 10, but can be changed by direct assignment only",
|
||||
fn() {
|
||||
assertEquals(EventEmitter.defaultMaxListeners, 10);
|
||||
new EventEmitter().setMaxListeners(20);
|
||||
assertEquals(EventEmitter.defaultMaxListeners, 10);
|
||||
EventEmitter.defaultMaxListeners = 20;
|
||||
assertEquals(EventEmitter.defaultMaxListeners, 20);
|
||||
EventEmitter.defaultMaxListeners = 10; //reset back to original value
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "addListener adds a listener, and listener count is correct",
|
||||
fn() {
|
||||
const testEmitter = new EventEmitter();
|
||||
testEmitter.on("event", shouldNeverBeEmitted);
|
||||
assertEquals(1, testEmitter.listenerCount("event"));
|
||||
testEmitter.on("event", shouldNeverBeEmitted);
|
||||
assertEquals(2, testEmitter.listenerCount("event"));
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "Emitted events are called synchronously in the order they were added",
|
||||
fn() {
|
||||
const testEmitter = new EventEmitter();
|
||||
const eventsFired = [];
|
||||
testEmitter.on("event", oneArg => {
|
||||
eventsFired.push("event(" + oneArg + ")");
|
||||
});
|
||||
testEmitter.on("event", (oneArg, twoArg) => {
|
||||
eventsFired.push("event(" + oneArg + ", " + twoArg + ")");
|
||||
});
|
||||
|
||||
testEmitter.on("non-event", shouldNeverBeEmitted);
|
||||
|
||||
testEmitter.on("event", (oneArg, twoArg, threeArg) => {
|
||||
eventsFired.push(
|
||||
"event(" + oneArg + ", " + twoArg + ", " + threeArg + ")"
|
||||
);
|
||||
});
|
||||
testEmitter.emit("event", 1, 2, 3);
|
||||
assertEquals(eventsFired, ["event(1)", "event(1, 2)", "event(1, 2, 3)"]);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "Registered event names are returned as strings or Sybols",
|
||||
fn() {
|
||||
const testEmitter = new EventEmitter();
|
||||
testEmitter.on("event", shouldNeverBeEmitted);
|
||||
testEmitter.on("event", shouldNeverBeEmitted);
|
||||
const sym = Symbol("symbol");
|
||||
testEmitter.on(sym, shouldNeverBeEmitted);
|
||||
assertEquals(testEmitter.eventNames(), ["event", sym]);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "You can set and get max listeners",
|
||||
fn() {
|
||||
const testEmitter = new EventEmitter();
|
||||
assertEquals(testEmitter.getMaxListeners(), 10);
|
||||
testEmitter.setMaxListeners(20);
|
||||
assertEquals(testEmitter.getMaxListeners(), 20);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "You can retrieve registered functions for an event",
|
||||
fn() {
|
||||
const testEmitter = new EventEmitter();
|
||||
testEmitter.on("someOtherEvent", shouldNeverBeEmitted);
|
||||
testEmitter.on("event", shouldNeverBeEmitted);
|
||||
const testFunction = (): void => {};
|
||||
testEmitter.on("event", testFunction);
|
||||
assertEquals(testEmitter.listeners("event"), [
|
||||
shouldNeverBeEmitted,
|
||||
testFunction
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "Off is alias for removeListener",
|
||||
fn() {
|
||||
const testEmitter = new EventEmitter();
|
||||
testEmitter.on("event", shouldNeverBeEmitted);
|
||||
assertEquals(testEmitter.listenerCount("event"), 1);
|
||||
testEmitter.off("event", shouldNeverBeEmitted);
|
||||
assertEquals(testEmitter.listenerCount("event"), 0);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "Event registration can be chained",
|
||||
fn() {
|
||||
const testEmitter = new EventEmitter();
|
||||
testEmitter
|
||||
.on("event", shouldNeverBeEmitted)
|
||||
.on("event", shouldNeverBeEmitted);
|
||||
assertEquals(testEmitter.listenerCount("event"), 2);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "Events can be registered to only fire once",
|
||||
fn() {
|
||||
let eventsFired = [];
|
||||
const testEmitter = new EventEmitter();
|
||||
//prove multiple emits on same event first (when registered with 'on')
|
||||
testEmitter.on("multiple event", () => {
|
||||
eventsFired.push("multiple event");
|
||||
});
|
||||
testEmitter.emit("multiple event");
|
||||
testEmitter.emit("multiple event");
|
||||
assertEquals(eventsFired, ["multiple event", "multiple event"]);
|
||||
|
||||
//now prove multiple events registered via 'once' only emit once
|
||||
eventsFired = [];
|
||||
testEmitter.once("single event", () => {
|
||||
eventsFired.push("single event");
|
||||
});
|
||||
testEmitter.emit("single event");
|
||||
testEmitter.emit("single event");
|
||||
assertEquals(eventsFired, ["single event"]);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name:
|
||||
"You can inject a listener into the start of the stack, rather than at the end",
|
||||
fn() {
|
||||
const eventsFired = [];
|
||||
const testEmitter = new EventEmitter();
|
||||
testEmitter.on("event", () => {
|
||||
eventsFired.push("first");
|
||||
});
|
||||
testEmitter.on("event", () => {
|
||||
eventsFired.push("second");
|
||||
});
|
||||
testEmitter.prependListener("event", () => {
|
||||
eventsFired.push("third");
|
||||
});
|
||||
testEmitter.emit("event");
|
||||
assertEquals(eventsFired, ["third", "first", "second"]);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: 'You can prepend a "once" listener',
|
||||
fn() {
|
||||
const eventsFired = [];
|
||||
const testEmitter = new EventEmitter();
|
||||
testEmitter.on("event", () => {
|
||||
eventsFired.push("first");
|
||||
});
|
||||
testEmitter.on("event", () => {
|
||||
eventsFired.push("second");
|
||||
});
|
||||
testEmitter.prependOnceListener("event", () => {
|
||||
eventsFired.push("third");
|
||||
});
|
||||
testEmitter.emit("event");
|
||||
testEmitter.emit("event");
|
||||
assertEquals(eventsFired, ["third", "first", "second", "first", "second"]);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "Remove all listeners, which can also be chained",
|
||||
fn() {
|
||||
const testEmitter = new EventEmitter();
|
||||
testEmitter.on("event", shouldNeverBeEmitted);
|
||||
testEmitter.on("event", shouldNeverBeEmitted);
|
||||
testEmitter.on("other event", shouldNeverBeEmitted);
|
||||
testEmitter.on("other event", shouldNeverBeEmitted);
|
||||
testEmitter.once("other event", shouldNeverBeEmitted);
|
||||
assertEquals(testEmitter.listenerCount("event"), 2);
|
||||
assertEquals(testEmitter.listenerCount("other event"), 3);
|
||||
|
||||
testEmitter.removeAllListeners("event").removeAllListeners("other event");
|
||||
|
||||
assertEquals(testEmitter.listenerCount("event"), 0);
|
||||
assertEquals(testEmitter.listenerCount("other event"), 0);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "Remove individual listeners, which can also be chained",
|
||||
fn() {
|
||||
const testEmitter = new EventEmitter();
|
||||
testEmitter.on("event", shouldNeverBeEmitted);
|
||||
testEmitter.on("event", shouldNeverBeEmitted);
|
||||
testEmitter.once("other event", shouldNeverBeEmitted);
|
||||
assertEquals(testEmitter.listenerCount("event"), 2);
|
||||
assertEquals(testEmitter.listenerCount("other event"), 1);
|
||||
|
||||
testEmitter.removeListener("other event", shouldNeverBeEmitted);
|
||||
assertEquals(testEmitter.listenerCount("event"), 2);
|
||||
assertEquals(testEmitter.listenerCount("other event"), 0);
|
||||
|
||||
testEmitter
|
||||
.removeListener("event", shouldNeverBeEmitted)
|
||||
.removeListener("event", shouldNeverBeEmitted);
|
||||
|
||||
assertEquals(testEmitter.listenerCount("event"), 0);
|
||||
assertEquals(testEmitter.listenerCount("other event"), 0);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "It is OK to try to remove non-existant listener",
|
||||
fn() {
|
||||
const testEmitter = new EventEmitter();
|
||||
|
||||
const madeUpEvent = (): void => {
|
||||
fail("Should never be called");
|
||||
};
|
||||
|
||||
testEmitter.on("event", shouldNeverBeEmitted);
|
||||
assertEquals(testEmitter.listenerCount("event"), 1);
|
||||
|
||||
testEmitter.removeListener("event", madeUpEvent);
|
||||
testEmitter.removeListener("non-existant event", madeUpEvent);
|
||||
|
||||
assertEquals(testEmitter.listenerCount("event"), 1);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "all listeners complete execution even if removed before execution",
|
||||
fn() {
|
||||
const testEmitter = new EventEmitter();
|
||||
let eventsProcessed = [];
|
||||
const listenerB = (): number => eventsProcessed.push("B");
|
||||
const listenerA = (): void => {
|
||||
eventsProcessed.push("A");
|
||||
testEmitter.removeListener("event", listenerB);
|
||||
};
|
||||
|
||||
testEmitter.on("event", listenerA);
|
||||
testEmitter.on("event", listenerB);
|
||||
|
||||
testEmitter.emit("event");
|
||||
assertEquals(eventsProcessed, ["A", "B"]);
|
||||
|
||||
eventsProcessed = [];
|
||||
testEmitter.emit("event");
|
||||
assertEquals(eventsProcessed, ["A"]);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: 'Raw listener will return event listener or wrapped "once" function',
|
||||
fn() {
|
||||
const testEmitter = new EventEmitter();
|
||||
const eventsProcessed = [];
|
||||
const listenerA = (): number => eventsProcessed.push("A");
|
||||
const listenerB = (): number => eventsProcessed.push("B");
|
||||
testEmitter.on("event", listenerA);
|
||||
testEmitter.once("once-event", listenerB);
|
||||
|
||||
const rawListenersForEvent = testEmitter.rawListeners("event");
|
||||
const rawListenersForOnceEvent = testEmitter.rawListeners("once-event");
|
||||
|
||||
assertEquals(rawListenersForEvent.length, 1);
|
||||
assertEquals(rawListenersForOnceEvent.length, 1);
|
||||
assertEquals(rawListenersForEvent[0], listenerA);
|
||||
assertEquals(
|
||||
(rawListenersForOnceEvent[0] as WrappedFunction).listener,
|
||||
listenerB
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name:
|
||||
"Once wrapped raw listeners may be executed multiple times, until the wrapper is executed",
|
||||
fn() {
|
||||
const testEmitter = new EventEmitter();
|
||||
let eventsProcessed = [];
|
||||
const listenerA = (): number => eventsProcessed.push("A");
|
||||
testEmitter.once("once-event", listenerA);
|
||||
|
||||
const rawListenersForOnceEvent = testEmitter.rawListeners("once-event");
|
||||
const wrappedFn: WrappedFunction = rawListenersForOnceEvent[0] as WrappedFunction;
|
||||
wrappedFn.listener();
|
||||
wrappedFn.listener();
|
||||
wrappedFn.listener();
|
||||
assertEquals(eventsProcessed, ["A", "A", "A"]);
|
||||
|
||||
eventsProcessed = [];
|
||||
wrappedFn(); // executing the wrapped listener function will remove it from the event
|
||||
assertEquals(eventsProcessed, ["A"]);
|
||||
assertEquals(testEmitter.listeners("once-event").length, 0);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "Can add once event listener to EventEmitter via standalone function",
|
||||
async fn() {
|
||||
const ee: EventEmitter = new EventEmitter();
|
||||
setTimeout(() => {
|
||||
ee.emit("event", 42, "foo");
|
||||
}, 0);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const valueArr: any[] = await once(ee, "event");
|
||||
assertEquals(valueArr, [42, "foo"]);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "Can add once event listener to EventTarget via standalone function",
|
||||
async fn() {
|
||||
const et: EventTarget = new EventTarget();
|
||||
setTimeout(() => {
|
||||
const event: Event = new Event("event", { composed: true });
|
||||
et.dispatchEvent(event);
|
||||
}, 0);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const eventObj: any[] = await once(et, "event");
|
||||
assert(!eventObj[0].isTrusted);
|
||||
}
|
||||
});
|
||||
|
||||
test({
|
||||
name: "Only valid integers are allowed for max listeners",
|
||||
fn() {
|
||||
const ee: EventEmitter = new EventEmitter();
|
||||
ee.setMaxListeners(0);
|
||||
assertThrows(
|
||||
() => {
|
||||
ee.setMaxListeners(-1);
|
||||
},
|
||||
Error,
|
||||
"must be >= 0"
|
||||
);
|
||||
assertThrows(
|
||||
() => {
|
||||
ee.setMaxListeners(3.45);
|
||||
},
|
||||
Error,
|
||||
"must be 'an integer'"
|
||||
);
|
||||
}
|
||||
});
|
|
@ -26,6 +26,7 @@ import * as nodeUtil from "./util.ts";
|
|||
import * as nodePath from "./path.ts";
|
||||
import * as nodeTimers from "./timers.ts";
|
||||
import * as nodeOs from "./os.ts";
|
||||
import * as nodeEvents from "./events.ts";
|
||||
|
||||
import * as path from "../path/mod.ts";
|
||||
import { assert } from "../testing/asserts.ts";
|
||||
|
@ -579,11 +580,14 @@ function createNativeModule(id: string, exports: any): Module {
|
|||
mod.loaded = true;
|
||||
return mod;
|
||||
}
|
||||
|
||||
nativeModulePolyfill.set("fs", createNativeModule("fs", nodeFS));
|
||||
nativeModulePolyfill.set("util", createNativeModule("util", nodeUtil));
|
||||
nativeModulePolyfill.set("events", createNativeModule("events", nodeEvents));
|
||||
nativeModulePolyfill.set("os", createNativeModule("os", nodeOs));
|
||||
nativeModulePolyfill.set("path", createNativeModule("path", nodePath));
|
||||
nativeModulePolyfill.set("timers", createNativeModule("timers", nodeTimers));
|
||||
nativeModulePolyfill.set("os", createNativeModule("os", nodeOs));
|
||||
nativeModulePolyfill.set("util", createNativeModule("util", nodeUtil));
|
||||
|
||||
function loadNativeModule(
|
||||
_filename: string,
|
||||
request: string
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
import { notImplemented } from "./_utils.ts";
|
||||
import { validateIntegerRange } from "./util.ts";
|
||||
import { EOL as fsEOL } from "../fs/eol.ts";
|
||||
|
||||
const SEE_GITHUB_ISSUE = "See https://github.com/denoland/deno/issues/3802";
|
||||
|
@ -117,7 +118,7 @@ export function freemem(): number {
|
|||
|
||||
/** Not yet implemented */
|
||||
export function getPriority(pid = 0): number {
|
||||
validateInt32(pid, "pid");
|
||||
validateIntegerRange(pid, "pid");
|
||||
notImplemented(SEE_GITHUB_ISSUE);
|
||||
}
|
||||
|
||||
|
@ -162,8 +163,8 @@ export function setPriority(pid: number, priority?: number): void {
|
|||
priority = pid;
|
||||
pid = 0;
|
||||
}
|
||||
validateInt32(pid, "pid");
|
||||
validateInt32(priority, "priority", -20, 19);
|
||||
validateIntegerRange(pid, "pid");
|
||||
validateIntegerRange(priority, "priority", -20, 19);
|
||||
|
||||
notImplemented(SEE_GITHUB_ISSUE);
|
||||
}
|
||||
|
@ -211,20 +212,3 @@ export const constants = {
|
|||
};
|
||||
|
||||
export const EOL = Deno.build.os == "win" ? fsEOL.CRLF : fsEOL.LF;
|
||||
|
||||
const validateInt32 = (
|
||||
value: number,
|
||||
name: string,
|
||||
min = -2147483648,
|
||||
max = 2147483647
|
||||
): void => {
|
||||
// The defaults for min and max correspond to the limits of 32-bit integers.
|
||||
if (!Number.isInteger(value)) {
|
||||
throw new Error(`${name} must be 'an integer' but was ${value}`);
|
||||
}
|
||||
if (value < min || value > max) {
|
||||
throw new Error(
|
||||
`${name} must be >= ${min} && <= ${max}. Value was ${value}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -45,3 +45,20 @@ export function isFunction(value: unknown): boolean {
|
|||
export function isRegExp(value: unknown): boolean {
|
||||
return value instanceof RegExp;
|
||||
}
|
||||
|
||||
export function validateIntegerRange(
|
||||
value: number,
|
||||
name: string,
|
||||
min = -2147483648,
|
||||
max = 2147483647
|
||||
): void {
|
||||
// The defaults for min and max correspond to the limits of 32-bit integers.
|
||||
if (!Number.isInteger(value)) {
|
||||
throw new Error(`${name} must be 'an integer' but was ${value}`);
|
||||
}
|
||||
if (value < min || value > max) {
|
||||
throw new Error(
|
||||
`${name} must be >= ${min} && <= ${max}. Value was ${value}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue