evnty
Version:
Async-first, reactive event handling library for complex event flows in browser and Node.js
113 lines (92 loc) • 2.93 kB
text/typescript
import { Action, Fn, Emitter, MaybePromise, Promiseable } from './types.js';
/**
* @internal
*/
export class Disposer {
#target?: Disposable;
#abortSignal?: AbortSignal;
constructor(target: Disposable, abortSignal?: AbortSignal) {
if (abortSignal?.aborted) return;
this.#target = target;
if (abortSignal) {
this.#abortSignal = abortSignal;
abortSignal.addEventListener('abort', this);
}
}
get disposed(): boolean {
return !this.#target;
}
[Symbol.dispose](): boolean {
if (!this.#target) return false;
this.#target = undefined;
// Stryker disable all: cleanup is memory optimization, no observable behavior after disposal
if (this.#abortSignal) {
this.#abortSignal.removeEventListener('abort', this);
this.#abortSignal = undefined;
}
// Stryker restore all
return true;
}
handleEvent(): void {
// Stryker disable next-line OptionalChaining: #target is always set when abort listener fires (synchronous code)
this.#target?.[Symbol.dispose]();
}
}
/**
* @internal
*/
export interface Async<T, R> extends Emitter<T, R>, Disposable {}
/**
* @internal
*/
export abstract class Async<T, R> implements Emitter<T, R>, Promiseable<T>, Promise<T>, AsyncIterator<T, void, void>, AsyncIterable<T> {
abstract [Symbol.toStringTag]: string;
abstract emit(value: T): R;
abstract receive(): Promise<T>;
dispose?(): void;
#sink?: Fn<[T], R>;
#disposer: Disposer;
constructor(abortSignal?: AbortSignal) {
this.#disposer = new Disposer(this, abortSignal);
}
get disposed(): boolean {
return this.#disposer.disposed;
}
get sink(): Fn<[T], R> {
return (this.#sink ??= this.emit.bind(this));
}
handleEvent(event: T) {
this.emit(event);
}
catch<OK = never>(onrejected?: Fn<[unknown], MaybePromise<OK>> | null): Promise<T | OK> {
return this.receive().catch(onrejected);
}
finally(onfinally?: Action | null): Promise<T> {
return this.receive().finally(onfinally);
}
then<OK = T, ERR = never>(onfulfilled?: Fn<[T], MaybePromise<OK>> | null, onrejected?: Fn<[unknown], MaybePromise<ERR>> | null): Promise<OK | ERR> {
return this.receive().then(onfulfilled, onrejected);
}
async next(): Promise<IteratorResult<T, void>> {
try {
const value = await this.receive();
return { value, done: false };
} catch {
return { value: undefined, done: true };
}
}
async return(): Promise<IteratorResult<T, void>> {
// Stryker disable next-line OptionalChaining: all subclasses define dispose()
this.dispose?.();
return { value: undefined, done: true };
}
[Symbol.asyncIterator](): AsyncIterator<T, void, void> {
return this;
}
[Symbol.dispose](): void {
if (this.#disposer[Symbol.dispose]()) {
// Stryker disable next-line OptionalChaining: all subclasses define dispose()
this.dispose?.();
}
}
}