spica
Version:
Supervisor, Coroutine, Channel, select, AtomicPromise, Cancellation, Cache, List, Queue, Stack, and some utils.
123 lines (118 loc) • 3.79 kB
text/typescript
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;