Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: use AsyncEventEmitter instead of EventEmitter #10710

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/discord.js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@discordjs/util": "workspace:^",
"@discordjs/ws": "workspace:^",
"@sapphire/snowflake": "3.5.5",
"@vladfrangu/async_event_emitter": "^2.4.6",
"discord-api-types": "^0.37.114",
"fast-deep-equal": "3.1.3",
"lodash.snakecase": "4.1.1",
Expand Down
8 changes: 4 additions & 4 deletions packages/discord.js/src/client/BaseClient.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
'use strict';

const EventEmitter = require('node:events');
const { REST } = require('@discordjs/rest');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { Routes } = require('discord-api-types/v10');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const { Options } = require('../util/Options');
const { flatten } = require('../util/Util');

/**
* The base class for all clients.
* @extends {EventEmitter}
* @extends {AsyncEventEmitter}
*/
class BaseClient extends EventEmitter {
class BaseClient extends AsyncEventEmitter {
constructor(options = {}) {
super({ captureRejections: true });
super();

if (typeof options !== 'object' || options === null) {
throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true);
Expand Down
10 changes: 5 additions & 5 deletions packages/discord.js/src/sharding/Shard.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use strict';

const EventEmitter = require('node:events');
const path = require('node:path');
const process = require('node:process');
const { setTimeout, clearTimeout } = require('node:timers');
const { setTimeout: sleep } = require('node:timers/promises');
const { SHARE_ENV } = require('node:worker_threads');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { DiscordjsError, ErrorCodes } = require('../errors');
const { ShardEvents } = require('../util/ShardEvents');
const { makeError, makePlainError } = require('../util/Util');
Expand All @@ -17,9 +17,9 @@ let Worker = null;
* A self-contained shard created by the {@link ShardingManager}. Each one has a {@link ChildProcess} that contains
* an instance of the bot and its {@link Client}. When its child process/worker exits for any reason, the shard will
* spawn a new one to replace it as necessary.
* @extends {EventEmitter}
* @extends {AsyncEventEmitter}
*/
class Shard extends EventEmitter {
class Shard extends AsyncEventEmitter {
constructor(manager, id) {
super();

Expand Down Expand Up @@ -445,7 +445,7 @@ class Shard extends EventEmitter {

/**
* Increments max listeners by one for a given emitter, if they are not zero.
* @param {EventEmitter|process} emitter The emitter that emits the events.
* @param {Worker|ChildProcess} emitter The emitter that emits the events.
* @private
*/
incrementMaxListeners(emitter) {
Expand All @@ -457,7 +457,7 @@ class Shard extends EventEmitter {

/**
* Decrements max listeners by one for a given emitter, if they are not zero.
* @param {EventEmitter|process} emitter The emitter that emits the events.
* @param {Worker|ChildProcess} emitter The emitter that emits the events.
* @private
*/
decrementMaxListeners(emitter) {
Expand Down
4 changes: 2 additions & 2 deletions packages/discord.js/src/sharding/ShardClientUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ class ShardClientUtil {

/**
* Increments max listeners by one for a given emitter, if they are not zero.
* @param {EventEmitter|process} emitter The emitter that emits the events.
* @param {Worker|ChildProcess} emitter The emitter that emits the events.
* @private
*/
incrementMaxListeners(emitter) {
Expand All @@ -254,7 +254,7 @@ class ShardClientUtil {

/**
* Decrements max listeners by one for a given emitter, if they are not zero.
* @param {EventEmitter|process} emitter The emitter that emits the events.
* @param {Worker|ChildProcess} emitter The emitter that emits the events.
* @private
*/
decrementMaxListeners(emitter) {
Expand Down
6 changes: 3 additions & 3 deletions packages/discord.js/src/sharding/ShardingManager.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use strict';

const EventEmitter = require('node:events');
const fs = require('node:fs');
const path = require('node:path');
const process = require('node:process');
const { setTimeout: sleep } = require('node:timers/promises');
const { Collection } = require('@discordjs/collection');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { Shard } = require('./Shard');
const { DiscordjsError, DiscordjsTypeError, DiscordjsRangeError, ErrorCodes } = require('../errors');
const { fetchRecommendedShardCount } = require('../util/Util');
Expand All @@ -17,9 +17,9 @@ const { fetchRecommendedShardCount } = require('../util/Util');
* process, and there are several useful methods that utilize it in order to simplify tasks that are normally difficult
* with sharding. It can spawn a specific number of shards or the amount that Discord suggests for the bot, and takes a
* path to your main bot script to launch for each one.
* @extends {EventEmitter}
* @extends {AsyncEventEmitter}
*/
class ShardingManager extends EventEmitter {
class ShardingManager extends AsyncEventEmitter {
/**
* The mode to spawn shards with for a {@link ShardingManager}. Can be either one of:
* * 'process' to use child processes
Expand Down
6 changes: 3 additions & 3 deletions packages/discord.js/src/structures/interfaces/Collector.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';

const EventEmitter = require('node:events');
const { setTimeout, clearTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection');
const { AsyncEventEmitter } = require('@vladfrangu/async_event_emitter');
const { DiscordjsTypeError, ErrorCodes } = require('../../errors');
const { flatten } = require('../../util/Util');

Expand All @@ -25,10 +25,10 @@ const { flatten } = require('../../util/Util');

/**
* Abstract class for defining a new Collector.
* @extends {EventEmitter}
* @extends {AsyncEventEmitter}
* @abstract
*/
class Collector extends EventEmitter {
class Collector extends AsyncEventEmitter {
constructor(client, options = {}) {
super();

Expand Down
123 changes: 18 additions & 105 deletions packages/discord.js/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Awaitable, JSONEncodable } from '@discordjs/util';
import { Collection, ReadonlyCollection } from '@discordjs/collection';
import { BaseImageURLOptions, ImageURLOptions, RawFile, REST, RESTOptions } from '@discordjs/rest';
import { WebSocketManager, WebSocketManagerOptions } from '@discordjs/ws';
import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
import {
APIActionRowComponent,
APIApplicationCommandInteractionData,
Expand Down Expand Up @@ -177,7 +178,6 @@ import {
GatewayVoiceChannelEffectSendDispatchData,
} from 'discord-api-types/v10';
import { ChildProcess } from 'node:child_process';
import { EventEmitter } from 'node:events';
import { Stream } from 'node:stream';
import { MessagePort, Worker } from 'node:worker_threads';
import {
Expand Down Expand Up @@ -515,7 +515,7 @@ export abstract class Base {
public valueOf(): string;
}

export class BaseClient extends EventEmitter implements AsyncDisposable {
export class BaseClient<Events extends {}> extends AsyncEventEmitter<Events> implements AsyncDisposable {
public constructor(options?: ClientOptions | WebhookClientOptions);
private decrementMaxListeners(): void;
private incrementMaxListeners(): void;
Expand Down Expand Up @@ -959,7 +959,7 @@ export type If<Value extends boolean, TrueResult, FalseResult = null> = Value ex
? FalseResult
: TrueResult | FalseResult;

export class Client<Ready extends boolean = boolean> extends BaseClient {
export class Client<Ready extends boolean = boolean> extends BaseClient<ClientEvents> {
public constructor(options: ClientOptions);
private actions: unknown;
private expectedGuilds: Set<Snowflake>;
Expand All @@ -977,18 +977,6 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
// This a technique used to brand the ready state. Or else we'll get `never` errors on typeguard checks.
private readonly _ready: Ready;

// Override inherited static EventEmitter methods, with added type checks for Client events.
public static once<Emitter extends EventEmitter, Event extends keyof ClientEvents>(
eventEmitter: Emitter,
eventName: Emitter extends Client ? Event : string | symbol,
options?: { signal?: AbortSignal | undefined },
): Promise<Emitter extends Client ? ClientEvents[Event] : any[]>;
public static on<Emitter extends EventEmitter, Event extends keyof ClientEvents>(
eventEmitter: Emitter,
eventName: Emitter extends Client ? Event : string | symbol,
options?: { signal?: AbortSignal | undefined },
): AsyncIterableIterator<Emitter extends Client ? ClientEvents[Event] : any[]>;

public application: If<Ready, ClientApplication>;
public channels: ChannelManager;
public get emojis(): BaseGuildEmojiManager;
Expand Down Expand Up @@ -1023,30 +1011,6 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
public login(token?: string): Promise<string>;
public isReady(): this is Client<true>;
public toJSON(): unknown;

public on<Event extends keyof ClientEvents>(event: Event, listener: (...args: ClientEvents[Event]) => void): this;
public on<Event extends string | symbol>(
event: Exclude<Event, keyof ClientEvents>,
listener: (...args: any[]) => void,
): this;

public once<Event extends keyof ClientEvents>(event: Event, listener: (...args: ClientEvents[Event]) => void): this;
public once<Event extends string | symbol>(
event: Exclude<Event, keyof ClientEvents>,
listener: (...args: any[]) => void,
): this;

public emit<Event extends keyof ClientEvents>(event: Event, ...args: ClientEvents[Event]): boolean;
public emit<Event extends string | symbol>(event: Exclude<Event, keyof ClientEvents>, ...args: unknown[]): boolean;

public off<Event extends keyof ClientEvents>(event: Event, listener: (...args: ClientEvents[Event]) => void): this;
public off<Event extends string | symbol>(
event: Exclude<Event, keyof ClientEvents>,
listener: (...args: any[]) => void,
): this;

public removeAllListeners<Event extends keyof ClientEvents>(event?: Event): this;
public removeAllListeners<Event extends string | symbol>(event?: Exclude<Event, keyof ClientEvents>): this;
}

export interface StickerPackFetchOptions {
Expand Down Expand Up @@ -1134,7 +1098,9 @@ export interface CollectorEventTypes<Key, Value, Extras extends unknown[] = []>
end: [collected: ReadonlyCollection<Key, Value>, reason: string];
}

export abstract class Collector<Key, Value, Extras extends unknown[] = []> extends EventEmitter {
export abstract class Collector<Key, Value, Extras extends unknown[] = []> extends AsyncEventEmitter<
CollectorEventTypes<Key, Value, Extras>
> {
protected constructor(client: Client<true>, options?: CollectorOptions<[Value, ...Extras]>);
private _timeout: NodeJS.Timeout | null;
private _idletimeout: NodeJS.Timeout | null;
Expand All @@ -1160,16 +1126,6 @@ export abstract class Collector<Key, Value, Extras extends unknown[] = []> exten
protected listener: (...args: any[]) => void;
public abstract collect(...args: unknown[]): Awaitable<Key | null>;
public abstract dispose(...args: unknown[]): Key | null;

public on<EventKey extends keyof CollectorEventTypes<Key, Value, Extras>>(
event: EventKey,
listener: (...args: CollectorEventTypes<Key, Value, Extras>[EventKey]) => void,
): this;

public once<EventKey extends keyof CollectorEventTypes<Key, Value, Extras>>(
event: EventKey,
listener: (...args: CollectorEventTypes<Key, Value, Extras>[EventKey]) => void,
): this;
}

export class ChatInputCommandInteraction<Cached extends CacheType = CacheType> extends CommandInteraction<Cached> {
Expand Down Expand Up @@ -2031,19 +1987,6 @@ export class InteractionCollector<Interaction extends CollectedInteraction> exte
public collect(interaction: Interaction): Snowflake;
public empty(): void;
public dispose(interaction: Interaction): Snowflake;
public on(event: 'collect' | 'dispose' | 'ignore', listener: (interaction: Interaction) => void): this;
almeidx marked this conversation as resolved.
Show resolved Hide resolved
public on(
event: 'end',
listener: (collected: ReadonlyCollection<Snowflake, Interaction>, reason: string) => void,
): this;
public on(event: string, listener: (...args: any[]) => void): this;

public once(event: 'collect' | 'dispose' | 'ignore', listener: (interaction: Interaction) => void): this;
public once(
event: 'end',
listener: (collected: ReadonlyCollection<Snowflake, Interaction>, reason: string) => void,
): this;
public once(event: string, listener: (...args: any[]) => void): this;
}

// tslint:disable-next-line no-empty-interface
Expand Down Expand Up @@ -2783,26 +2726,6 @@ export class ReactionCollector extends Collector<Snowflake | string, MessageReac
public collect(reaction: MessageReaction, user: User): Snowflake | string | null;
public dispose(reaction: MessageReaction, user: User): Snowflake | string | null;
public empty(): void;

public on(
almeidx marked this conversation as resolved.
Show resolved Hide resolved
event: 'collect' | 'dispose' | 'remove' | 'ignore',
listener: (reaction: MessageReaction, user: User) => void,
): this;
public on(
event: 'end',
listener: (collected: ReadonlyCollection<Snowflake, MessageReaction>, reason: string) => void,
): this;
public on(event: string, listener: (...args: any[]) => void): this;

public once(
almeidx marked this conversation as resolved.
Show resolved Hide resolved
event: 'collect' | 'dispose' | 'remove' | 'ignore',
listener: (reaction: MessageReaction, user: User) => void,
): this;
public once(
event: 'end',
listener: (collected: ReadonlyCollection<Snowflake, MessageReaction>, reason: string) => void,
): this;
public once(event: string, listener: (...args: any[]) => void): this;
}

export class ReactionEmoji extends Emoji {
Expand Down Expand Up @@ -2997,15 +2920,15 @@ export interface ShardEventTypes {
spawn: [process: ChildProcess | Worker];
}

export class Shard extends EventEmitter {
export class Shard extends AsyncEventEmitter<ShardEventTypes> {
private constructor(manager: ShardingManager, id: number);
private _evals: Map<string, Promise<unknown>>;
private _exitListener: (...args: any[]) => void;
private _fetches: Map<string, Promise<unknown>>;
private _handleExit(respawn?: boolean, timeout?: number): void;
private _handleMessage(message: unknown): void;
private incrementMaxListeners(emitter: EventEmitter | ChildProcess): void;
private decrementMaxListeners(emitter: EventEmitter | ChildProcess): void;
private incrementMaxListeners(emitter: Worker | ChildProcess): void;
private decrementMaxListeners(emitter: Worker | ChildProcess): void;

public args: string[];
public execArgv: string[];
Expand All @@ -3027,24 +2950,14 @@ export class Shard extends EventEmitter {
public respawn(options?: { delay?: number; timeout?: number }): Promise<ChildProcess>;
public send(message: unknown): Promise<Shard>;
public spawn(timeout?: number): Promise<ChildProcess>;

public on<Event extends keyof ShardEventTypes>(
event: Event,
listener: (...args: ShardEventTypes[Event]) => void,
): this;

public once<Event extends keyof ShardEventTypes>(
event: Event,
listener: (...args: ShardEventTypes[Event]) => void,
): this;
}

export class ShardClientUtil {
private constructor(client: Client<true>, mode: ShardingManagerMode);
private _handleMessage(message: unknown): void;
private _respond(type: string, message: unknown): void;
private incrementMaxListeners(emitter: EventEmitter | ChildProcess): void;
private decrementMaxListeners(emitter: EventEmitter | ChildProcess): void;
private incrementMaxListeners(emitter: Worker | ChildProcess): void;
private decrementMaxListeners(emitter: Worker | ChildProcess): void;

public client: Client;
public get count(): number;
Expand Down Expand Up @@ -3073,7 +2986,11 @@ export class ShardClientUtil {
public static shardIdForGuildId(guildId: Snowflake, shardCount: number): number;
}

export class ShardingManager extends EventEmitter {
export interface ShardingManagerEvents {
shardCreate: [shard: Shard];
}

export class ShardingManager extends AsyncEventEmitter<ShardingManagerEvents> {
public constructor(file: string, options?: ShardingManagerOptions);
private _performOnShards(method: string, args: readonly unknown[]): Promise<unknown[]>;
private _performOnShards(method: string, args: readonly unknown[], shard: number): Promise<unknown>;
Expand Down Expand Up @@ -3105,10 +3022,6 @@ export class ShardingManager extends EventEmitter {
public fetchClientValues(prop: string, shard: number): Promise<unknown>;
public respawnAll(options?: MultipleShardRespawnOptions): Promise<Collection<number, Shard>>;
public spawn(options?: MultipleShardSpawnOptions): Promise<Collection<number, Shard>>;

public on(event: 'shardCreate', listener: (shard: Shard) => void): this;

public once(event: 'shardCreate', listener: (shard: Shard) => void): this;
}

export interface FetchRecommendedShardCountOptions {
Expand Down Expand Up @@ -3692,8 +3605,8 @@ export class Webhook<Type extends WebhookType = WebhookType> {
}

// tslint:disable-next-line no-empty-interface
export interface WebhookClient extends WebhookFields, BaseClient {}
export class WebhookClient extends BaseClient {
export interface WebhookClient extends WebhookFields, BaseClient<{}> {}
export class WebhookClient extends BaseClient<{}> {
public constructor(data: WebhookClientData, options?: WebhookClientOptions);
public readonly client: this;
public options: WebhookClientOptions;
Expand Down
4 changes: 2 additions & 2 deletions packages/discord.js/typings/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1306,9 +1306,9 @@ client.on('guildCreate', async g => {
);
});

// EventEmitter static method overrides
// Event emitter static method overrides
expectType<Promise<[Client<true>]>>(Client.once(client, 'clientReady'));
expectType<AsyncIterableIterator<[Client<true>]>>(Client.on(client, 'clientReady'));
expectAssignable<AsyncIterableIterator<[Client<true>]>>(Client.on(client, 'clientReady'));

client.login('absolutely-valid-token');

Expand Down
Loading
Loading