From d42ab51583b9816a6f633f532c6f5d92aca35030 Mon Sep 17 00:00:00 2001 From: lisez Date: Sat, 25 May 2024 01:54:22 +0800 Subject: [PATCH 1/3] feat: enable typing declaration --- scripts/build_npm.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/build_npm.ts b/scripts/build_npm.ts index cb687e8..eb703cf 100644 --- a/scripts/build_npm.ts +++ b/scripts/build_npm.ts @@ -12,7 +12,6 @@ await emptyDir(output); await build({ test: false, - declaration: false, typeCheck: "both", entryPoints: ["./modules/xemitter.ts"], outDir: output, From 0396baff4b64495a62a3df0c315a7d0ad656dbfa Mon Sep 17 00:00:00 2001 From: lisez Date: Sat, 25 May 2024 01:54:51 +0800 Subject: [PATCH 2/3] feat: fix async and add node examples --- .gitignore | 1 + examples/node/async-event-example.mjs | 17 ++++++ examples/node/conjoined-event-example.mjs | 17 ++++++ examples/node/mixed-handlers-example.mjs | 20 +++++++ examples/node/package-lock.json | 21 ++++++++ examples/node/package.json | 14 +++++ examples/node/sync-event-example.mjs | 10 ++++ examples/xemitter.ts | 14 ----- modules/conjoin_emitter.ts | 56 ++++++++++---------- modules/core_emitter.ts | 19 +++---- modules/emitter.ts | 21 ++++++-- tests/deno/multiple_events_test.ts | 64 +++++++++++++++++++++-- tests/deno/single_event_test.ts | 36 +++++++++++-- 13 files changed, 243 insertions(+), 67 deletions(-) create mode 100644 examples/node/async-event-example.mjs create mode 100644 examples/node/conjoined-event-example.mjs create mode 100644 examples/node/mixed-handlers-example.mjs create mode 100644 examples/node/package-lock.json create mode 100644 examples/node/package.json create mode 100644 examples/node/sync-event-example.mjs delete mode 100644 examples/xemitter.ts diff --git a/.gitignore b/.gitignore index e848d8f..ee0ac76 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .coverage dist/ +node_modules/ diff --git a/examples/node/async-event-example.mjs b/examples/node/async-event-example.mjs new file mode 100644 index 0000000..b97e4f8 --- /dev/null +++ b/examples/node/async-event-example.mjs @@ -0,0 +1,17 @@ +import { Xemitter } from "xevt"; + +const emitter = new Xemitter(); +emitter.onAsync( + "event", + async (data) => + new Promise((res) => { + setTimeout(() => { + console.log("Event:", data); + res(); + }, 100); + }), +); + +for (let i = 0; i < 10; i++) { + await emitter.emit("event", i); +} diff --git a/examples/node/conjoined-event-example.mjs b/examples/node/conjoined-event-example.mjs new file mode 100644 index 0000000..d6a159d --- /dev/null +++ b/examples/node/conjoined-event-example.mjs @@ -0,0 +1,17 @@ +import { Xemitter } from "xevt"; + +const emitter = new Xemitter(); +emitter.on("event1", (data) => { + console.log("Event1:", data); +}); +emitter.on("event2", (data) => { + console.log("Event2:", data); +}); +emitter.conjoin(["event1", "event2"], () => { + console.log("Conjoined event"); +}); + +for (let i = 0; i < 10; i++) { + emitter.emit("event1", i); + emitter.emit("event2", i); +} diff --git a/examples/node/mixed-handlers-example.mjs b/examples/node/mixed-handlers-example.mjs new file mode 100644 index 0000000..ba45377 --- /dev/null +++ b/examples/node/mixed-handlers-example.mjs @@ -0,0 +1,20 @@ +import { Xemitter } from "xevt"; + +const emitter = new Xemitter(); +emitter.on("event", (data) => { + console.log("sync hanlder:", data); +}); +emitter.onAsync( + "event", + async (data) => + new Promise((res) => { + setTimeout(() => { + console.log("async handler:", data); + res(); + }, 100); + }), +); + +for (let i = 0; i < 10; i++) { + await emitter.emit("event", i); +} diff --git a/examples/node/package-lock.json b/examples/node/package-lock.json new file mode 100644 index 0000000..c7e3984 --- /dev/null +++ b/examples/node/package-lock.json @@ -0,0 +1,21 @@ +{ + "name": "xevt-node-example", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "xevt-node-example", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "xevt": "*" + } + }, + "node_modules/xevt": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/xevt/-/xevt-0.3.5.tgz", + "integrity": "sha512-9kmyupyyESfhOJY1w7fchG4ve89EIQ4STiryWUAA6txLALNhmPureVhtYSXw+thPiX5rqAb0G6Uyd1N8uIIp2A==" + } + } +} diff --git a/examples/node/package.json b/examples/node/package.json new file mode 100644 index 0000000..f3a3c19 --- /dev/null +++ b/examples/node/package.json @@ -0,0 +1,14 @@ +{ + "name": "xevt-node-example", + "version": "1.0.0", + "description": "example for xevt", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "dependencies": { + "xevt": "*" + } +} diff --git a/examples/node/sync-event-example.mjs b/examples/node/sync-event-example.mjs new file mode 100644 index 0000000..14872ab --- /dev/null +++ b/examples/node/sync-event-example.mjs @@ -0,0 +1,10 @@ +import { Xemitter } from "xevt"; + +const emitter = new Xemitter(); +emitter.on("event", (data) => { + console.log("Event:", data); +}); + +for (let i = 0; i < 10; i++) { + emitter.emit("event", i); +} diff --git a/examples/xemitter.ts b/examples/xemitter.ts deleted file mode 100644 index 8f4911b..0000000 --- a/examples/xemitter.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Xemitter } from "../modules/xemitter.ts"; - -const emitter = new Xemitter(); -let count = 0; -emitter.on(["event1", "event2"], () => { - count++; - console.log(count); -}, { once: true }); -emitter.emit("event1"); -emitter.emit("event2"); -emitter.emit("event1"); -emitter.emit("event2"); - -console.log(count); diff --git a/modules/conjoin_emitter.ts b/modules/conjoin_emitter.ts index 259af49..0804826 100644 --- a/modules/conjoin_emitter.ts +++ b/modules/conjoin_emitter.ts @@ -12,18 +12,19 @@ import type { import { CoreEmitter } from "./core_emitter.ts"; import { Emitter } from "./emitter.ts"; -export class ConjoinEmitter extends CoreEmitter - implements XConjoinEmitter { +export class ConjoinEmitter + extends CoreEmitter + implements XConjoinEmitter +{ private nameIndex: Map = new Map(); private conjoinedNames: Map = new Map(); private indexCounter = 0; private waitingQueue: PendingConjoinEvent[] = []; private idleQueue: PendingConjoinEvent[] = []; private errorEmitter = new Emitter(); + private prevEvents?: Promise; - private internalConjoinOn( - signature: EventHandlerSignature, - ) { + private internalConjoinOn(signature: EventHandlerSignature) { if (signature.name.length < 2) { throw new RangeError("Conjoin events must have at least two events"); } @@ -43,9 +44,7 @@ export class ConjoinEmitter extends CoreEmitter this.onBySignature(name, signature); } - getConjoinedEventName( - events: EventName[] | ConjoinEvents, - ): EventName { + getConjoinedEventName(events: EventName[] | ConjoinEvents): EventName { const keys = ([] as EventName[]).concat(events); keys.sort(); return keys.join("."); @@ -120,29 +119,26 @@ export class ConjoinEmitter extends CoreEmitter if (!event) return; const handlers = this.handlers.get(event)?.slice() || []; - handlers.filter((e) => e.options?.once).forEach((e) => { - this.offByHandler(event, e.handler); - }); - const isSync = handlers.every((e) => !e.options?.async); + handlers + .filter((e) => e.options?.once) + .forEach((e) => { + this.offByHandler(event, e.handler); + }); + try { if (handlers.length) { - if (isSync) { - this.internalExec(0, handlers); - return this.exec(pointer + 1, events); - } else { - return this.internalExec(0, handlers).catch((err: unknown) => { - throw err; - }).then(() => { - return this.exec(pointer + 1, events); - }); + const result = this.internalExec(0, handlers); + if (result) { + return result.then(() => this.exec(pointer + 1, events)); } + return this.exec(pointer + 1, events); } } catch (e) { this.errorEmitter.emit("error", e); } } - emit(event: EventName): void { + emit(event: EventName): any { if (!this.nameIndex.has(event)) return; let executing: EventName[] = []; @@ -154,19 +150,23 @@ export class ConjoinEmitter extends CoreEmitter nextIdle = nextIdle.concat(idle); } - if (executing.length) this.exec(0, executing); - this.idleQueue = nextIdle; this.waitingQueue = executing.map((e) => ({ event: e, conjoined: this.conjoinedNames.get(e)?.slice() || [], })); + + if (executing.length) { + if (this.prevEvents) { + this.prevEvents = this.prevEvents.then(() => this.exec(0, executing)); + } else { + this.prevEvents = this.exec(0, executing); + } + } + return this.prevEvents; } - off( - event: ConjoinEvents, - handler?: EventHandler, - ): void { + off(event: ConjoinEvents, handler?: EventHandler): void { const key = this.getConjoinedEventName(event); if (handler) { return this.offByHandler(key, handler); diff --git a/modules/core_emitter.ts b/modules/core_emitter.ts index ca1b8d0..70381b4 100644 --- a/modules/core_emitter.ts +++ b/modules/core_emitter.ts @@ -10,9 +10,7 @@ import type { export abstract class CoreEmitter implements XCoreEmitter { protected handlers: RegisteredHandlers; - constructor( - handlers?: RegisteredHandlers, - ) { + constructor(handlers?: RegisteredHandlers) { this.handlers = handlers || new Map(); } @@ -30,9 +28,9 @@ export abstract class CoreEmitter implements XCoreEmitter { const profile = signatures[pointer]; if (!profile) return; if (profile.options?.async) { - return profile.handler(...args).then(() => - this.internalExec(pointer + 1, signatures, ...args) - ); + return profile + .handler(...args) + .then(() => this.internalExec(pointer + 1, signatures, ...args)); } profile.handler(...args); return this.internalExec(pointer + 1, signatures, ...args); @@ -45,8 +43,8 @@ export abstract class CoreEmitter implements XCoreEmitter { if ( signature.options?.async && // @ts-ignore TS7053 - ((signature.handler[Symbol.toStringTag] !== "AsyncFunction") && - !("then" in signature.handler)) + signature.handler[Symbol.toStringTag] !== "AsyncFunction" && + !("then" in signature.handler) ) { throw new Error("Async handler must be a promise or thenable"); } @@ -72,10 +70,7 @@ export abstract class CoreEmitter implements XCoreEmitter { if (idx !== -1) handlers.splice(idx, 1); } - abstract off( - event: T, - handler?: EventHandler, - ): void; + abstract off(event: T, handler?: EventHandler): void; get removeEventListener() { return this.off; diff --git a/modules/emitter.ts b/modules/emitter.ts index fb45a2d..612d1a3 100644 --- a/modules/emitter.ts +++ b/modules/emitter.ts @@ -11,6 +11,8 @@ import { CoreEmitter } from "./core_emitter.ts"; export const EmitDone = Symbol("emit_done"); export class Emitter extends CoreEmitter implements XevtEmitter { + private prevEvents?: Promise; + on(event: EventName, handler: EventHandler, options?: Partial) { const signature = { name: event, @@ -46,14 +48,23 @@ export class Emitter extends CoreEmitter implements XevtEmitter { this.on("error", handler); } - emit(event: EventName, ...args: any[]): void { + emit(event: EventName, ...args: any[]): any { const handlers = this.handlers.get(event)?.slice() || []; - handlers.filter((e) => e.options?.once).forEach((e) => { - this.offByHandler(event, e.handler); - }); + handlers + .filter((e) => e.options?.once) + .forEach((e) => { + this.offByHandler(event, e.handler); + }); try { - this.internalExec(0, handlers, ...args); + if (this.prevEvents) { + this.prevEvents = this.prevEvents.then(() => + this.internalExec(0, handlers, ...args), + ); + } else { + this.prevEvents = this.internalExec(0, handlers, ...args); + } + return this.prevEvents; } catch (err) { this.emit("error", err); } finally { diff --git a/tests/deno/multiple_events_test.ts b/tests/deno/multiple_events_test.ts index ff7215d..e90ca29 100644 --- a/tests/deno/multiple_events_test.ts +++ b/tests/deno/multiple_events_test.ts @@ -23,6 +23,40 @@ describe("Xemitter - multiple events", () => { assert(count === 2, `Expected 2, got ${count}`); }); + it("should listen multiple handlers", () => { + const emitter = new Xemitter(); + const result: number[] = []; + emitter.on(["event1", "event2"], () => { + result.push(1); + }); + emitter.on(["event1", "event2"], () => { + result.push(2); + }); + emitter.emit("event1"); + emitter.emit("event2"); + emitter.emit("event1"); + emitter.emit("event2"); + + assertEquals(result, [1, 2, 1, 2]); + }); + + it("should listen multiple async handlers", async () => { + const emitter = new Xemitter(); + const result: number[] = []; + emitter.conjoinAsync(["event1", "event2"], async () => { + result.push(1); + }); + emitter.conjoinAsync(["event1", "event2"], async () => { + result.push(2); + }); + emitter.emit("event1"); + emitter.emit("event2"); + emitter.emit("event1"); + emitter.emit("event2"); + await delay(0); + assertEquals(result, [1, 2, 1, 2]); + }); + it("should listen multiple events with addEventListener", () => { const emitter = new Xemitter(); let count = 0; @@ -70,9 +104,13 @@ describe("Xemitter - multiple events", () => { it("should listen multiple events once", () => { const emitter = new Xemitter(); let count = 0; - emitter.on(["event1", "event2"], () => { - count++; - }, { once: true }); + emitter.on( + ["event1", "event2"], + () => { + count++; + }, + { once: true }, + ); emitter.emit("event1"); emitter.emit("event2"); emitter.emit("event1"); @@ -88,7 +126,7 @@ describe("Xemitter - multiple events", () => { setTimeout(() => { result++; resolve(true); - }, 10) + }, 10), ); }); emitter.emit("event1"); @@ -102,4 +140,22 @@ describe("Xemitter - multiple events", () => { await delay(100); assertEquals(result, 4); }); + + it("mix handlers", async () => { + const emitter = new Xemitter(); + const result: number[] = []; + emitter.conjoin(["event1", "event2"], async () => { + result.push(1); + }); + emitter.conjoinAsync(["event1", "event2"], async () => { + result.push(2); + }); + + for (let i = 0; i < 5; i++) { + emitter.emit("event1"); + emitter.emit("event2"); + } + await delay(100); + assertEquals(result, [1, 2, 1, 2, 1, 2, 1, 2, 1, 2]); + }); }); diff --git a/tests/deno/single_event_test.ts b/tests/deno/single_event_test.ts index b4f5a74..c4ecad7 100644 --- a/tests/deno/single_event_test.ts +++ b/tests/deno/single_event_test.ts @@ -59,9 +59,13 @@ describe("Xemitter - single event", () => { it("should listen event once", () => { const emitter = new Xemitter(); let count = 0; - emitter.on("event", () => { - count++; - }, { once: true }); + emitter.on( + "event", + () => { + count++; + }, + { once: true }, + ); emitter.emit("event"); emitter.emit("event"); @@ -87,7 +91,7 @@ describe("Xemitter - single event", () => { setTimeout(() => { result.push(arg); resolve(true); - }, 10) + }, 10), ); }); emitter.emit("event", 1); @@ -101,4 +105,28 @@ describe("Xemitter - single event", () => { await delay(100); assertEquals(result, [1, 2, 3, 4, 5, 6, 7, 8]); }); + + it("mix handlers", async () => { + const emitter = new Xemitter(); + const result: number[] = []; + emitter.on("event", (data) => { + result.push(data); + }); + emitter.onAsync( + "event", + async (data) => + new Promise((res) => { + setTimeout(() => { + result.push(data); + res(true); + }, 10); + }), + ); + + for (let i = 0; i < 5; i++) { + await emitter.emit("event", i); + } + await delay(100); + assertEquals(result, [0, 0, 1, 1, 2, 2, 3, 3, 4, 4]); + }); }); From a748cbf8483f4232ba01caae2e134affc2d09cee Mon Sep 17 00:00:00 2001 From: lisez Date: Sat, 25 May 2024 02:04:53 +0800 Subject: [PATCH 3/3] feat: fix testing and update readme --- README.md | 97 ++++++++++++++++++++++++++++++ tests/deno/multiple_events_test.ts | 2 +- tests/deno/single_event_test.ts | 2 +- 3 files changed, 99 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9d29da7..18e972e 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,101 @@ another event emiiter. +features: + +- support async event. +- support conjoined events. +- support mixed async/sync handlers. + [![Coverage Status](https://coveralls.io/repos/github/lisez/xevt/badge.svg?branch=feature/0.2.0)](https://coveralls.io/github/lisez/xevt?branch=feature/0.2.0) + +## Installation + +```bash +npm install xevt +``` + +Then: + +```typescript +import { Xemitter } from "xevt"; +``` + +## Usage + +### Basic usage + +```typescript +const emitter = new Xemitter(); + +let result = 0; +emitter.on("event", () => { + result++; +}); +emitter.emit("event"); +``` + +### Async event + +```typescript +const emitter = new Xemitter(); + +let result = 0; +emitter.onAsync("event", async () => { + result++; +}); +await emitter.emit("event"); +``` + +### Conjoined event + +```typescript +const emitter = new Xemitter(); + +let count = 0; +emitter.on(["event1", "event2"], () => { + count++; +}); +emitter.emit("event1"); +emitter.emit("event2"); +``` + +### Mixed async/sync handlers + +```typescript +const emitter = new Xemitter(); +const result: number[] = []; +emitter.on("event", (data) => { + result.push(data); +}); +emitter.onAsync( + "event", + async (data) => + new Promise((res) => { + setTimeout(() => { + result.push(data); + res(true); + }, 10); + }), +); + +for (let i = 0; i < 5; i++) { + emitter.emit("event", i); +} +``` + +```typescript +const emitter = new Xemitter(); +const result: number[] = []; +emitter.conjoin(["event1", "event2"], async () => { + result.push(1); +}); +emitter.conjoinAsync(["event1", "event2"], async () => { + result.push(2); +}); + +for (let i = 0; i < 5; i++) { + emitter.emit("event1"); + emitter.emit("event2"); +} +``` diff --git a/tests/deno/multiple_events_test.ts b/tests/deno/multiple_events_test.ts index e90ca29..1056048 100644 --- a/tests/deno/multiple_events_test.ts +++ b/tests/deno/multiple_events_test.ts @@ -144,7 +144,7 @@ describe("Xemitter - multiple events", () => { it("mix handlers", async () => { const emitter = new Xemitter(); const result: number[] = []; - emitter.conjoin(["event1", "event2"], async () => { + emitter.conjoin(["event1", "event2"], () => { result.push(1); }); emitter.conjoinAsync(["event1", "event2"], async () => { diff --git a/tests/deno/single_event_test.ts b/tests/deno/single_event_test.ts index c4ecad7..b3e5966 100644 --- a/tests/deno/single_event_test.ts +++ b/tests/deno/single_event_test.ts @@ -124,7 +124,7 @@ describe("Xemitter - single event", () => { ); for (let i = 0; i < 5; i++) { - await emitter.emit("event", i); + emitter.emit("event", i); } await delay(100); assertEquals(result, [0, 0, 1, 1, 2, 2, 3, 3, 4, 4]);