Skip to content

Commit

Permalink
Merge branch 'release/v0.3.6'
Browse files Browse the repository at this point in the history
  • Loading branch information
lisez committed May 24, 2024
2 parents 6b30792 + 2a7468d commit b773aab
Show file tree
Hide file tree
Showing 15 changed files with 340 additions and 68 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.coverage
dist/
node_modules/
97 changes: 97 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
```
17 changes: 17 additions & 0 deletions examples/node/async-event-example.mjs
Original file line number Diff line number Diff line change
@@ -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);
}
17 changes: 17 additions & 0 deletions examples/node/conjoined-event-example.mjs
Original file line number Diff line number Diff line change
@@ -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);
}
20 changes: 20 additions & 0 deletions examples/node/mixed-handlers-example.mjs
Original file line number Diff line number Diff line change
@@ -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);
}
21 changes: 21 additions & 0 deletions examples/node/package-lock.json

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

14 changes: 14 additions & 0 deletions examples/node/package.json
Original file line number Diff line number Diff line change
@@ -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": "*"
}
}
10 changes: 10 additions & 0 deletions examples/node/sync-event-example.mjs
Original file line number Diff line number Diff line change
@@ -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);
}
14 changes: 0 additions & 14 deletions examples/xemitter.ts

This file was deleted.

56 changes: 28 additions & 28 deletions modules/conjoin_emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@ import type {
import { CoreEmitter } from "./core_emitter.ts";
import { Emitter } from "./emitter.ts";

export class ConjoinEmitter extends CoreEmitter<ConjoinEvents>
implements XConjoinEmitter {
export class ConjoinEmitter
extends CoreEmitter<ConjoinEvents>
implements XConjoinEmitter
{
private nameIndex: Map<EventName, number> = new Map();
private conjoinedNames: Map<EventName, ConjoinEvents> = new Map();
private indexCounter = 0;
private waitingQueue: PendingConjoinEvent[] = [];
private idleQueue: PendingConjoinEvent[] = [];
private errorEmitter = new Emitter();
private prevEvents?: Promise<any>;

private internalConjoinOn(
signature: EventHandlerSignature<ConjoinEvents>,
) {
private internalConjoinOn(signature: EventHandlerSignature<ConjoinEvents>) {
if (signature.name.length < 2) {
throw new RangeError("Conjoin events must have at least two events");
}
Expand All @@ -43,9 +44,7 @@ export class ConjoinEmitter extends CoreEmitter<ConjoinEvents>
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(".");
Expand Down Expand Up @@ -120,29 +119,26 @@ export class ConjoinEmitter extends CoreEmitter<ConjoinEvents>
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[] = [];
Expand All @@ -154,19 +150,23 @@ export class ConjoinEmitter extends CoreEmitter<ConjoinEvents>
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);
Expand Down
19 changes: 7 additions & 12 deletions modules/core_emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import type {
export abstract class CoreEmitter<T> implements XCoreEmitter<T> {
protected handlers: RegisteredHandlers;

constructor(
handlers?: RegisteredHandlers,
) {
constructor(handlers?: RegisteredHandlers) {
this.handlers = handlers || new Map();
}

Expand All @@ -30,9 +28,9 @@ export abstract class CoreEmitter<T> implements XCoreEmitter<T> {
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);
Expand All @@ -45,8 +43,8 @@ export abstract class CoreEmitter<T> implements XCoreEmitter<T> {
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");
}
Expand All @@ -72,10 +70,7 @@ export abstract class CoreEmitter<T> implements XCoreEmitter<T> {
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;
Expand Down
Loading

0 comments on commit b773aab

Please sign in to comment.