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

Add TypeScript definition #17

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
137 changes: 137 additions & 0 deletions Emittery.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
export = Emittery;

declare class Emittery {
/**
* Subscribe to an event.
*
* Returns an unsubscribe method.
*
* Using the same listener multiple times for the same event will result
* in only one method call per emitted event.
*/
on(eventName: string, listener: (eventData?: any) => any): Emittery.UnsubscribeFn;

/**
* Remove an event subscription.
*
* If you don't pass in a `listener`, it will remove all listeners for that
* event.
*/
off(eventName: string, listener?: (eventData?: any) => any): void;

/**
* Subscribe to an event only once. It will be unsubscribed after the first
* event.
*
* Returns a promise for the event data when `eventName` is emitted.
*/
once(eventName: string): Promise<any>;

/**
* Trigger an event asynchronously, optionally with some data. Listeners
* are called in the order they were added, but execute concurrently.
*
* Returns a promise for when all the event listeners are done. *Done*
* meaning executed if synchronous or resolved when an
* async/promise-returning function. You usually wouldn't want to wait for
* this, but you could for example catch possible errors. If any of the
* listeners throw/reject, the returned promise will be rejected with the
* error, but the other listeners will not be affected.
*
* Returns a promise for when all the event listeners are done.
*/
emit(eventName: string, eventData?: any): Promise<void>;

/**
* Same as `emit()`, but it waits for each listener to resolve before
* triggering the next one. This can be useful if your events depend on each
* other. Although ideally they should not. Prefer `emit()` whenever
* possible.
*
* If any of the listeners throw/reject, the returned promise will be
* rejected with the error and the remaining listeners will *not* be called.
*
* Returns a promise for when all the event listeners are done.
*/
emitSerial(eventName: string, eventData?: any): Promise<void>;

/**
* Subscribe to be notified about any event.
*
* Returns a method to unsubscribe.
*/
onAny(listener: (eventName: string, eventData?: any) => any): Emittery.UnsubscribeFn;

/**
Remove an `onAny` subscription.

If you don't pass in a `listener`, it will remove all `onAny` subscriptions.
*/
offAny(listener?: (eventName: string, eventData?: any) => any): void;

/**
* Clear all event listeners on the instance.
*/
clear(): void;

/**
* The number of listeners for the `eventName` or all events if not
* specified.
*/
listenerCount(eventName?: string): number;
}

declare namespace Emittery {
/**
* Removes an event subscription.
*/
type UnsubscribeFn = () => void;

/**
* Maps event names to their emitted data type.
*/
interface Events {
[eventName: string]: any;
}

/**
* Async event emitter.
*
* Must list supported events and the data type they emit, if any.
*
* For example:
*
* ```ts
* import Emittery = require('emittery');
*
* const ee = new Emittery.Typed<{value: string}, 'open' | 'close'>();
*
* ee.emit('open');
* ee.emit('value', 'foo\n');
* ee.emit('value', 1); // TS compilation error
* ee.emit('end'); // TS compilation error
* ```
*/
class Typed<EventDataMap extends Events, EmptyEvents = never> extends Emittery {
on<Name extends keyof EventDataMap>(eventName: Name, listener: (eventData: EventDataMap[Name]) => any): Emittery.UnsubscribeFn;
on<Name extends EmptyEvents>(eventName: Name, listener: () => any): Emittery.UnsubscribeFn;

once<Name extends keyof EventDataMap>(eventName: Name): Promise<EventDataMap[Name]>;
once<Name extends EmptyEvents>(eventName: Name): Promise<void>;

off<Name extends keyof EventDataMap>(eventName: Name, listener?: (eventData: EventDataMap[Name]) => any): void;
off<Name extends EmptyEvents>(eventName: Name, listener?: () => any): void;

onAny<Name extends keyof EventDataMap>(listener: (eventName: Name, eventData: EventDataMap[Name]) => any): Emittery.UnsubscribeFn;
onAny<Name extends EmptyEvents>(listener: (eventName: Name) => any): Emittery.UnsubscribeFn;

offAny<Name extends keyof EventDataMap>(listener?: (eventName: Name, eventData: EventDataMap[Name]) => any): void;
offAny<Name extends EmptyEvents>(listener?: (eventName: Name) => any): void;

emit<Name extends keyof EventDataMap>(eventName: Name, eventData: EventDataMap[Name]): Promise<void>;
emit<Name extends EmptyEvents>(eventName: Name): Promise<void>;

emitSerial<Name extends keyof EventDataMap>(eventName: Name, eventData: EventDataMap[Name]): Promise<void>;
emitSerial<Name extends EmptyEvents>(eventName: Name): Promise<void>;
}
}
1 change: 1 addition & 0 deletions examples/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/clocktyped.js
79 changes: 79 additions & 0 deletions examples/clock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env node
'use strict';

const Emittery = require('..');

class Clock extends Emittery {
constructor() {
super();
this.startedAt = 0;
this.timer = null;
}

tick() {
if (!this.timer) {
this.emit('error', new Error('Clock has not been started'));
return;
}

const now = Date.now();
const duration = now - this.startedAt;

this.emit('tick', {duration, now});
}

start() {
if (this.timer) {
throw new Error('Clock has already been started');
}

this.startedAt = Date.now();
this.timer = setInterval(this.tick.bind(this), 1000);

this.emit('start');
}

stop() {
if (this.timer) {
clearInterval(this.timer);
}

this.startedAt = 0;
this.timer = null;

this.emit('stop');
}
}

function onTick({duration}) {
console.log(Math.floor(duration / 1000));

if (duration >= 6000) {
stop();
}
}

function onError(err) {
process.exitCode = 1;
console.error(err);
stop();
}

const timer = new Clock();
const offTick = timer.on('tick', onTick);
const offError = timer.on('error', onError);

function stop() {
offTick();
offError();
timer.stop();
}

timer.start();
// Prints:
// 1
// 2
// 3
// 4
// 5
// 6
93 changes: 93 additions & 0 deletions examples/clocktyped.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env npx ts-node
import {setInterval} from 'timers';
import Emittery = require('..');

interface TickData {
now: number;
duration: number;
}

// Map Clock's events emitting data to the type of their data.
type EventDataMap = {
tick: TickData,
error: Error
};

// List of event which do not required data
type EmptyEvents = 'start' | 'stop';

class Clock extends Emittery.Typed<EventDataMap, EmptyEvents> {
private startedAt = 0;
private timer: NodeJS.Timer | null = null;

public constructor() {
super();
}

private tick() {
if (!this.timer) {
this.emit('error', new Error('Clock has not been started'));
return;
}

const now = Date.now();
const duration = now - this.startedAt;

this.emit('tick', {duration, now});
}

public start() {
if (this.timer) {
throw new Error('Clock has already been started');
}

this.startedAt = Date.now();
this.timer = setInterval(this.tick.bind(this), 1000);

this.emit('start');
}

public stop() {
if (this.timer) {
clearInterval(this.timer);
}

this.startedAt = 0;
this.timer = null;

this.emit('stop');
}
}

function onTick({duration}: TickData) {
console.log(Math.floor(duration / 1000));

if (duration >= 6000) {
stop();
}
}

function onError(err: Error) {
process.exitCode = 1;
console.error(err);
stop();
}

const timer = new Clock();
const offTick = timer.on('tick', onTick);
const offError = timer.on('error', onError);

function stop() {
offTick();
offError();
timer.stop();
}

timer.start();
// Prints:
// 1
// 2
// 3
// 4
// 5
// 6
18 changes: 18 additions & 0 deletions examples/emit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env node

'use strict';

const Emittery = require('..');

const myEmitter = new Emittery();

// Emit event in next tick
myEmitter.emit('event');

// Register listener
myEmitter.on('event', () => console.log('an event occurred!'));
myEmitter.onAny(eventName => console.log('"%s" event occurred!', eventName));

// Prints:
// an event occurred!
// "event" event occurred!
18 changes: 18 additions & 0 deletions examples/emitonce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env node

'use strict';

const Emittery = require('..');

const myEmitter = new Emittery();

// Emit events in next tick
myEmitter.emit('event', 1);
myEmitter.emit('event', 2);

// Register listener for only the one event
myEmitter.once('event')
.then(count => console.log('an event occurred (#%d).', count));

// Prints:
// an event occurred (#1).
18 changes: 18 additions & 0 deletions examples/eventdata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env node

'use strict';

const Emittery = require('../');

const myEmitter = new Emittery();

// Only accept one event data parameter
myEmitter.emit('event', {a: true, b: true}, 'not', 'supported');

// Does not provide a context either.
myEmitter.on('event', function ({a, b}, ...args) {
console.log(a, b, args, this);
});

// Prints:
// true true [] undefined
13 changes: 11 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function assertEventName(eventName) {
}
}

module.exports = class Emittery {
class Emittery {
constructor() {
this._events = new Map();
this._anyEvents = new Set();
Expand Down Expand Up @@ -105,4 +105,13 @@ module.exports = class Emittery {

return count;
}
};
}

// Subclass used to encourage TS users to type their events.
Emittery.Typed = class extends Emittery {};
Object.defineProperty(Emittery.Typed, 'Typed', {
enumerable: false,
value: undefined
});

module.exports = Emittery;
2 changes: 2 additions & 0 deletions legacy.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Emittery = require('./Emittery')
export = Emittery
Loading