UNPKG

spica

Version:

Supervisor, Coroutine, Channel, select, AtomicPromise, Cancellation, Cache, List, Queue, Stack, and some utils.

123 lines (118 loc) 3.79 kB
import { AtomicPromise, Internal, internal } from './promise'; import { Maybe, Just, Nothing } from './maybe'; import { Either, Left, Right } from './either'; import { noop } from './function'; import { causeAsyncException } from './exception'; export interface Canceller<L = undefined> { readonly cancel: { (reason: L): void; (this: Canceller<undefined>, reason?: L): void; }; readonly close: (reason?: unknown) => void; } export interface Cancellee<L = undefined> extends Promise<L> { isAlive(): boolean; isCancelled(): boolean; readonly register: (listener: Listener<L>) => () => void; readonly promise: <T>(value: T) => AtomicPromise<T>; readonly maybe: <T>(value: T) => Maybe<T>; readonly either: <R>(value: R) => Either<L, R>; } type Listener<L> = (reason: L) => void; const enum State { alive, cancelled, closed, } export interface Cancellation<L> extends AtomicPromise<L> { } export class Cancellation<L = undefined> implements Canceller<L>, Cancellee<L>, AtomicPromise<L> { public readonly [Symbol.toStringTag]: string = 'Cancellation'; constructor(cancellees?: Iterable<Cancellee<L>>) { if (cancellees) for (const cancellee of cancellees) { cancellee.register(this.cancel); } } private state: State = State.alive; private reason: unknown = undefined; private handlers: Listener<L>[] = []; public readonly [internal] = new Internal<L>(); public isAlive(): boolean { return this.state === State.alive; } public isCancelled(): boolean { return this.state === State.cancelled; } public isClosed(): boolean { return this.state === State.closed; } private register$(listener: Listener<L>): () => void { const { handlers } = this; if (!this.isAlive() && handlers.length === 0) { this.isCancelled() && handler(this.reason as L); return noop; } handlers.push(handler); return () => void (listener = noop); function handler(reason: L): void { try { listener(reason); } catch (reason) { causeAsyncException(reason); } } } public get register(): (listener: Listener<L>) => () => void { return listener => this.register$(listener); } private cancel$(reason: L): void { if (!this.isAlive()) return; this.state = State.cancelled; this.reason = reason; for (let { handlers } = this, i = 0; i < handlers.length; ++i) { handlers[i](reason!); } this.handlers = []; assert(Object.freeze(this.handlers)); this[internal].resolve(reason!); } public get cancel(): Canceller<L>['cancel'] { return reason => this.cancel$(reason!); } public close$(reason?: unknown): void { if (!this.isAlive()) return; this.state = State.closed; this.reason = reason; this.handlers = []; assert(Object.freeze(this.handlers)); this[internal].reject(reason); } public get close(): (reason?: unknown) => void { return reason => this.close$(reason); } public get promise(): <T>(value: T) => AtomicPromise<T> { return value => this.isCancelled() ? AtomicPromise.reject(this.reason) : AtomicPromise.resolve(value); } public get maybe(): <T>(value: T) => Maybe<T> { return value => Just(value) .bind(value => this.isCancelled() ? Nothing : Just(value)); } public get either(): <R>(value: R) => Either<L, R> { return value => Right<L, typeof value>(value) .bind(value => this.isCancelled() ? Left(this.reason as L) : Right(value)); } } Cancellation.prototype.then = AtomicPromise.prototype.then; Cancellation.prototype.catch = AtomicPromise.prototype.catch; Cancellation.prototype.finally = AtomicPromise.prototype.finally;