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

On dual #21

Merged
merged 13 commits into from
May 27, 2024
Merged
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
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ another event emiiter.
features:

- can listen async/conjoined event.
- support to mixed async/sync handlers.
- support to mixed async/sync handlers
- conditional event handlers. (onDual)
- return unscriber when you subscribe an event.

[![Coverage Status](https://coveralls.io/repos/github/lisez/xevt/badge.svg)](https://coveralls.io/github/lisez/xevt) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
Expand Down Expand Up @@ -48,6 +49,45 @@ emitter.on("event", async () => {
await emitter.emit("event");
```

### Conditional event handlers.

IMPORTANT: conditional handlers not supported to conjoined event.

```typescript
const emitter = new Xevt();
const result: any[] = [];
emitter.on("event", (arg: number) => {
result.push(arg);
return arg % 2 === 0;
});

emitter.onDual("event", {
true: async () => {
result.push("first");
},
});

emitter.onDual("event", {
false: async () => {
result.push("fail");
},
});

emitter.onDual("event", {
true: () => {
result.push(100);
},
false: () => {
result.push(99);
},
});

emitter.emit("event", 1);
emitter.emit("event", 2);
await delay(10);
// [1, "fail", 99, 2, "first", 100]
```

### Conjoined event

IMPORTANT: conjoined events are not supported any arguments in handlers.
Expand Down
19 changes: 11 additions & 8 deletions modules/conjoin_emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,18 @@ export class ConjoinEmitter extends CoreEmitter<ConjoinEvents>
if (!this.hasEvent(event)) return;

const executing = this.consume(event);
if (executing.length) {
if (this.debug) this.logger.debug("conjoined", executing);
if (this.prevEvents) {
this.prevEvents = this.prevEvents.then(() => this.exec(executing));
} else {
this.prevEvents = this.exec(executing);
}
if (!executing.length) return;
if (this.debug) this.logger.debug("conjoined", executing);

const next = () => {
this.prevEvents = this.exec(executing);
return this.prevEvents;
};

if (this.prevEvents instanceof Promise) {
return Promise.resolve(this.prevEvents).then(next);
}
return this.prevEvents;
return next();
}

off(event: ConjoinEvents, handler?: EventHandler): void {
Expand Down
6 changes: 5 additions & 1 deletion modules/core_emitter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {
DualEventHandler,
ErrorHandler,
EventHandler,
EventHandlerSignature,
Expand Down Expand Up @@ -59,7 +60,10 @@ export abstract class CoreEmitter<T> implements XCoreEmitter<T> {
this.handlers.delete(event);
}

protected offByHandler(event: EventName, handler: EventHandler): void {
protected offByHandler(
event: EventName,
handler: EventHandler | DualEventHandler,
): void {
const handlers = this.handlers.get(event);
if (!handlers?.length) return;
const idx = handlers.findIndex((h) => h.handler === handler);
Expand Down
31 changes: 22 additions & 9 deletions modules/emitter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type {
DualEventHandler,
DualEventHandlerSignature,
ErrorHandler,
EventHandler,
EventName,
Expand All @@ -7,7 +9,7 @@ import type {
} from "modules/types.ts";

import { CoreEmitter } from "modules/core_emitter.ts";
import { SequenceRunner } from "modules/runners/sequence.ts";
import { StepRunner } from "modules/runners/step.ts";

export const EmitDone = Symbol("emit_done");

Expand All @@ -26,6 +28,22 @@ export class Emitter extends CoreEmitter<EventName> implements XevtEmitter {
return this.onBySignature(event, signature);
}

onDual(
event: EventName,
handler: DualEventHandler,
options?: Partial<EventOptions>,
) {
const signature: DualEventHandlerSignature<any> = {
name: event,
handler,
options: {
once: options?.once || event === EmitDone,
dual: true,
},
};
return this.onBySignature(event, signature);
}

get addEventListener() {
return this.on;
}
Expand All @@ -37,18 +55,13 @@ export class Emitter extends CoreEmitter<EventName> implements XevtEmitter {
emit(event: EventName, ...args: any[]): any {
if (this.debug) this.logger.debug("emit", event, args);

const handlers = this.handlers.get(event)?.slice() || [];
for (const e of handlers.filter((e) => e.options?.once)) {
this.offByHandler(event, e.handler);
}

try {
if (this.prevEvents) {
if (this.prevEvents instanceof Promise) {
this.prevEvents = this.prevEvents.then(() =>
new SequenceRunner(handlers).exec(0, ...args)
this.prevEvents = new StepRunner(this.handlers).exec(event, args)
);
} else {
this.prevEvents = new SequenceRunner(handlers).exec(0, ...args);
this.prevEvents = new StepRunner(this.handlers).exec(event, args);
}
return this.prevEvents;
} catch (err) {
Expand Down
13 changes: 12 additions & 1 deletion modules/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import type { ConjoinEvents, EventName } from "modules/types.ts";
import type {
ConjoinEvents,
DualEventHandlerSignature,
EventHandlerSignature,
EventName,
} from "modules/types.ts";

export function isConjoinEvents(
event: EventName | EventName[] | ConjoinEvents,
): event is ConjoinEvents {
return Array.isArray(event) && event.length > 1;
}

export function isDualHandler(
handler: EventHandlerSignature<any>,
): handler is DualEventHandlerSignature<any> {
return !!handler.options && "dual" in handler.options;
}
61 changes: 61 additions & 0 deletions modules/runners/dual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type {
DualEventHandlerSignature,
GeneralEventHandlerSignature,
} from "modules/types.ts";

import { SequenceRunner } from "modules/runners/sequence.ts";

/**
* Run a dual event handler.
*/
export class DualRunner<N = any> {
/**
* Create a new instance of the DualRunner.
* @param handlers The dual handler profile.
*/
constructor(
private handlers: DualEventHandlerSignature<N>[],
) {
this.handlers = handlers;
}

/**
* Conditionally filter the handlers.
* @param condition The condition to filter the handlers.
* @returns The filtered handlers.
*/
private filterHandlers(
condition: boolean,
): GeneralEventHandlerSignature<N>[] {
const handlers: GeneralEventHandlerSignature<N>[] = [];
for (const p of this.handlers) {
// @ts-ignore TS2538
if (typeof p.handler[condition] === "function") {
// @ts-ignore TS2538
handlers.push({ ...p, handler: p.handler[condition] });
}
}
return handlers;
}

/**
* Execute the dual handler that corresponds to the result.
* @param result The result of the handler.
*/
private dualExec(result: any) {
const handlers = this.filterHandlers(!!result);
if (!handlers.length) return;
return new SequenceRunner(handlers).exec([result]);
}

/**
* Execute the dual handler.
* @param args The arguments to pass to the dual handler.
*/
exec(result: any) {
if (result instanceof Promise) {
return Promise.resolve(result).then((res) => this.dualExec(res));
}
return this.dualExec(result);
}
}
28 changes: 13 additions & 15 deletions modules/runners/sequence.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,43 @@
import type { EventHandlerSignature } from "modules/types.ts";
import type { GeneralEventHandlerSignature } from "modules/types.ts";

import { SingleRunner } from "modules/runners/single.ts";

/**
* Run handlers in sequence.
*/
export class SequenceRunner<
T extends EventHandlerSignature<any> = EventHandlerSignature<any>,
N extends GeneralEventHandlerSignature<any> = GeneralEventHandlerSignature<
any
>,
> {
/**
* Create a new instance of the SequenceRunner.
* @param handlers The handlers to run.
*/
constructor(private handlers: T[]) {
constructor(private handlers: N[]) {
this.handlers = handlers;
}

/**
* Execute the handlers in sequence.
* @param pointer The current handler index.
* @param args The arguments to pass to the handlers.
* @param index The current handler index.
*/
exec(
pointer: number = 0,
...args: Parameters<T["handler"]>
args: Parameters<N["handler"]>,
index: number = 0,
): void | Promise<void> {
const profile = this.handlers[pointer];
const profile = this.handlers[index];
if (!profile) return;

const result = new SingleRunner<T>(profile).exec(
...args,
);
const result = new SingleRunner<N>(profile).exec(args) as any;

/**
* Wait for the handler to finish before moving to the next handler.
*/
if (profile.options?.async) {
return Promise.resolve(result).then(() =>
this.exec(pointer + 1, ...args)
);
if (profile.options?.async || result instanceof Promise) {
return Promise.resolve(result).then(() => this.exec(args, index + 1));
}
return this.exec(pointer + 1, ...args);
return this.exec(args, index + 1);
}
}
32 changes: 5 additions & 27 deletions modules/runners/series.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import type {
EventHandlerSignature,
EventName,
RegisteredHandlers,
} from "modules/types.ts";
import type { EventName, RegisteredHandlers } from "modules/types.ts";

import { SequenceRunner } from "modules/runners/sequence.ts";
import { StepRunner } from "modules/runners/step.ts";

/**
* Run handlers each in series.
Expand All @@ -18,18 +14,6 @@ export class SeriesRunner {
this.handlers = handlers;
}

/**
* Remove a handler.
* @param key The event name.
* @param profile The handler profile.
*/
private remove(key: EventName, profile: EventHandlerSignature<any>): void {
const handlers = this.handlers.get(key);
if (!handlers) return;
const idx = handlers.findIndex((h) => h.handler === profile.handler);
if (idx !== -1) handlers.splice(idx, 1);
}

/**
* Execute the handlers in series.
* @param series The series of event names.
Expand All @@ -39,15 +23,9 @@ export class SeriesRunner {
const key = series[idx];
if (!key) return;

const handlers = this.handlers.get(key)?.slice() || [];
for (const p of handlers.filter((p) => !!p.options?.once)) {
this.remove(key, p);
}
if (!handlers.length) return;

const result = new SequenceRunner(handlers).exec(0);
if (result) {
return result.then(() => this.exec(series, idx + 1));
const step = new StepRunner(this.handlers).exec(key);
if (step instanceof Promise) {
return Promise.resolve(step).then(() => this.exec(series, idx + 1));
}
return this.exec(series, idx + 1);
}
Expand Down
10 changes: 5 additions & 5 deletions modules/runners/single.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { EventHandlerSignature } from "modules/types.ts";
import type { GeneralEventHandlerSignature } from "modules/types.ts";

/**
* The result of a single runner.
Expand All @@ -11,7 +11,9 @@ export type SingleRunnerResult<T extends (...args: any[]) => any> = ReturnType<
* Run a handler.
*/
export class SingleRunner<
T extends EventHandlerSignature<any> = EventHandlerSignature<any>,
T extends GeneralEventHandlerSignature<any> = GeneralEventHandlerSignature<
any
>,
> {
/**
* Create a new instance of the SingleRunner.
Expand All @@ -25,9 +27,7 @@ export class SingleRunner<
* Execute the handler.
* @param args The arguments to pass to the handler.
*/
exec(
...args: Parameters<T["handler"]>
): SingleRunnerResult<T["handler"]> {
exec(args: Parameters<T["handler"]>): SingleRunnerResult<T["handler"]> {
return this.profile.handler(...args);
}
}
Loading
Loading