Skip to content

Commit

Permalink
Added Ability to define strict events
Browse files Browse the repository at this point in the history
  • Loading branch information
ardalanamini committed Apr 12, 2019
1 parent 3445479 commit ef4eae7
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 31 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@

---

## [v1.1.2](https://github.com/foxifyjs/events/releases/tag/v1.1.2) - _(2019-04-12)_

- :star2: Added Ability to define strict events.

## [v1.1.1](https://github.com/foxifyjs/events/releases/tag/v1.1.1) - _(2019-04-10)_

- :star2: Minimize code for browsers.
Expand Down
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Events <!-- omit in toc -->

`@foxify/events` is an EventEmitter alternative for Node.js and browser that has been optimized to be faster than the native version, (why not?!).
`@foxify/events` is a high performance EventEmitter alternative for Node.js and browser that has been optimized to be faster than the native version, (why not?!).

[![NPM Version](https://img.shields.io/npm/v/@foxify/events.svg)](https://www.npmjs.com/package/@foxify/events)
[![TypeScript Version](https://img.shields.io/npm/types/@foxify/events.svg)](https://www.typescriptlang.org)
Expand All @@ -24,11 +24,13 @@ This module is API compatible with the EventEmitter that ships by default with N
- The `newListener` and `removeListener` events have been removed as they are useful only in some uncommon use-cases.
- The `setMaxListeners` and `getMaxListeners` methods are not available.
- Support for custom context for events so there is no need to use `bind`.
- Support for strict events in TypeScript.

## Table of Contents <!-- omit in toc -->

- [Installation](#installation)
- [Usage](#usage)
- [Strict events](#strict-events)
- [Contextual emits](#contextual-emits)
- [Versioning](#versioning)
- [Authors](#authors)
Expand All @@ -48,6 +50,35 @@ const { EventEmitter } = require("@foxify/events");

For the API documentation, please follow the official Node.js [documentation](https://nodejs.org/api/events.html).

### Strict events

> "error" event is always defined by default because of its different behavior
```ts
interface Events {
foo: (bar: string) => void;
}

const eventEmitter = new EventEmitter<Events>();

// or

class Emitter extends EventEmitter<Events> {
}

const eventEmitter = new Emitter();

// Throws an error, since this event requires the "bar" argument of type "string"
eventEmitter.emit("foo");

// Works just fine
eventEmitter.emit("foo");

// Works just fine. so don't worry about "ImplicitAny" config, since type of "bar" is defined as "string"
eventEmitter.on("foo", bar => 1);

```

### Contextual emits

We've upgraded the API of the `on`, `once`, `addListener`, `prependListener` and
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@foxify/events",
"version": "1.1.1",
"description": "An EventEmitter alternative for Node.js and browser",
"version": "1.1.2",
"description": "A high performance EventEmitter alternative for Node.js and browser",
"author": "Ardalan Amini <[email protected]> (https://ardalanamini.com)",
"license": "MIT",
"homepage": "https://github.com/foxifyjs/events#readme",
Expand Down Expand Up @@ -40,10 +40,9 @@
"codecov": "npm run build && npm run coverage -- --runInBand && codecov",
"benchmarks": "find benchmarks -name '*.js' -exec benchmarks/start.sh {} \\;"
},
"dependencies": {},
"devDependencies": {
"@types/jest": "^24.0.11",
"@types/node": "^11.13.2",
"@types/node": "^11.13.4",
"benchmark": "^2.1.4",
"codecov": "^3.3.0",
"drip": "^1.4.0",
Expand Down
125 changes: 103 additions & 22 deletions src/EventEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ function assertEvent(event: string | symbol) {
);
}

function assertListener(listener: (...args: any[]) => void) {
function assertListener(listener: EventEmitter.DefaultListener) {
assert(typeof listener === "function", "'listener' must be a function");
}

function addListener(
emitter: EventEmitter,
event: string | symbol,
listener: (...args: any[]) => void,
listener: EventEmitter.DefaultListener,
context: any = emitter,
prepend: boolean,
once: boolean,
Expand All @@ -38,45 +38,72 @@ function addListener(
}

namespace EventEmitter {
export type DefaultListener = (...args: any[]) => void;

export type DefaultEvents = {
[E in string | symbol]: EventEmitter.DefaultListener
};

export type Event<Events extends {}> = Extract<
keyof Events,
string | symbol
>;

export type EmitArgs<T> = [T] extends [(...args: infer U) => any]
? U
: [T] extends [void]
? []
: [T];

export interface Listeners {
[event: string]: Item[] | undefined;
}
}

interface EventEmitter {
on(
event: string | symbol,
listener: (...args: any[]) => void,
interface EventEmitter<Events extends {} = EventEmitter.DefaultEvents> {
on(event: "error", listener: (error: Error) => void, context?: any): this;
on<K extends EventEmitter.Event<Events>>(
event: K,
listener: Events[K],
context?: any,
): this;

off(event: string | symbol, listener: (...args: any[]) => void): this;
off(event: "error", listener: (error: Error) => void): this;
off<K extends EventEmitter.Event<Events>>(
event: K,
listener: Events[K],
): this;
}

class EventEmitter {
class EventEmitter<Events extends {} = EventEmitter.DefaultEvents> {
protected _listeners: EventEmitter.Listeners = {};

public eventNames() {
public eventNames(): Array<EventEmitter.Event<Events>> {
const listeners = this._listeners;

return Object.keys(listeners).filter(
event => listeners[event] !== undefined,
);
) as any;
}

public rawListeners(event: "error"): Item[];
public rawListeners<K extends EventEmitter.Event<Events>>(event: K): Item[];
public rawListeners(event: string | symbol) {
assertEvent(event);

return this._listeners[event as string] || [];
}

public listeners(event: "error"): EventEmitter.DefaultListener[];
public listeners<K extends EventEmitter.Event<Events>>(
event: K,
): Array<Events[K]>;
public listeners(event: string | symbol) {
const listeners = this.rawListeners(event);
const listeners = this.rawListeners(event as any);
const length = listeners.length;

if (!length) return [];

// tslint:disable-next-line:prefer-array-literal
const ret = new Array(length);

for (let i = 0; i < length; i++) {
Expand All @@ -86,14 +113,19 @@ class EventEmitter {
return ret;
}

public listenerCount(event: "error"): number;
public listenerCount<K extends EventEmitter.Event<Events>>(event: K): number;
public listenerCount(event: string | symbol) {
return this.rawListeners(event).length;
return this.rawListeners(event as any).length;
}

public emit(event: "error", error: Error): boolean;
public emit(event: string | symbol, ...args: any[]): boolean;
public emit<K extends EventEmitter.Event<Events>>(
event: K,
...args: EventEmitter.EmitArgs<Events[K]>
): boolean;
public emit(event: string | symbol, ...args: any[]) {
const listeners = this.rawListeners(event);
const listeners = this.rawListeners(event as any);
const length = listeners.length;

if (!length) {
Expand All @@ -105,46 +137,90 @@ class EventEmitter {
for (let i = 0; i < length; i++) {
const { listener, context, once } = listeners[i];

if (once) this.removeListener(event, listener);
if (once) this.removeListener(event as any, listener as any);

listener.apply(context, args);
}

return true;
}

public addListener(
event: "error",
listener: (error: Error) => void,
context?: any,
): this;
public addListener<K extends EventEmitter.Event<Events>>(
event: K,
listener: Events[K],
context?: any,
): this;
public addListener(
event: string | symbol,
listener: (...args: any[]) => void,
listener: EventEmitter.DefaultListener,
context?: any,
) {
return addListener(this, event, listener, context, false, false);
}

public once(
event: "error",
listener: (error: Error) => void,
context?: any,
): this;
public once<K extends EventEmitter.Event<Events>>(
event: K,
listener: Events[K],
context?: any,
): this;
public once(
event: string | symbol,
listener: (...args: any[]) => void,
listener: EventEmitter.DefaultListener,
context?: any,
) {
return addListener(this, event, listener, context, false, true);
}

public prependListener(
event: "error",
listener: (error: Error) => void,
context?: any,
): this;
public prependListener<K extends EventEmitter.Event<Events>>(
event: K,
listener: Events[K],
context?: any,
): this;
public prependListener(
event: string | symbol,
listener: (...args: any[]) => void,
listener: EventEmitter.DefaultListener,
context?: any,
) {
return addListener(this, event, listener, context, true, false);
}

public prependOnceListener(
event: "error",
listener: (error: Error) => void,
context?: any,
): this;
public prependOnceListener<K extends EventEmitter.Event<Events>>(
event: K,
listener: Events[K],
context?: any,
): this;
public prependOnceListener(
event: string | symbol,
listener: (...args: any[]) => void,
listener: EventEmitter.DefaultListener,
context?: any,
) {
return addListener(this, event, listener, context, true, true);
}

public removeAllListeners(event?: "error"): this;
public removeAllListeners<K extends EventEmitter.Event<Events>>(
event: K,
): this;
public removeAllListeners(event?: string | symbol) {
assert(
event === undefined ||
Expand All @@ -166,13 +242,18 @@ class EventEmitter {
return this;
}

public removeListener(event: "error", listener: (error: Error) => void): this;
public removeListener<K extends EventEmitter.Event<Events>>(
event: K,
listener: Events[K],
): this;
public removeListener(
event: string | symbol,
listener: (...args: any[]) => void,
listener: EventEmitter.DefaultListener,
) {
assertListener(listener);

let listeners = this.rawListeners(event);
let listeners = this.rawListeners(event as any);
const length = listeners.length;

if (!length) return this;
Expand Down
1 change: 1 addition & 0 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"no-increment-decrement": false,
"no-parameter-reassignment": false,
"interface-name": false,
"prefer-array-literal": false,
"quotemark": [true, "double"],
"variable-name": [
true,
Expand Down

0 comments on commit ef4eae7

Please sign in to comment.