interface Status { message?: string; cause?: unknown; Ok(): boolean; ok(): boolean; Fail(): boolean; fail(): boolean; Of(cls: any): boolean; of(cls: any): boolean; } /** * Error result */ export class Err extends Error { Ok(): boolean { return false; } ok(): boolean { return this.Ok(); } Fail(): boolean { return true; } fail(): boolean { return this.Fail(); } Of(cls: any): boolean { return this.cause instanceof cls || this instanceof cls; } of(cls: any): boolean { return this.Of(cls); } static fromCatch(error: any): Status { const e = new Err(typeof error === "string" ? error : error.message); e.cause = error; e.stack = error.stack; return e; } } /** * Successful result */ export class Ok { Ok(): boolean { return true; } ok(): boolean { return this.Ok(); } Fail(): boolean { return false; } fail(): boolean { return this.Fail(); } Of(cls: any): boolean { return this instanceof cls; } of(cls: any): boolean { return this.Of(cls); } toString(): string { return "Ok"; } } interface IResult { 0: T; 1: Status; value: T; status: Status; Of(cls: any): boolean; Ok(): boolean; Fail(): boolean; } type IterResult = { [Symbol.asyncIterator](): AsyncGenerator< Awaited>, void, unknown >; [Symbol.iterator](): Generator, void, unknown>; }; class Result extends Array implements IResult { 0: T; 1: Status; constructor(result: T, status: Status) { super(2); this[0] = result; this[1] = status; } get value(): T { return this[0]; } get status(): Status { return this[1]; } Ok(): boolean { return (this[1] as Status).Ok(); } ok(): boolean { return this.Ok(); } Fail(): boolean { return (this[1] as Status).Fail(); } fail(): boolean { return this.Fail(); } Of(cls: any): boolean { return (this[1] as Status).Of(cls); } of(cls: any): boolean { return this.Of(cls); } Iter(): IterResult { const value = this.value; const that = this; if ( typeof value !== "object" && !( typeof (value as any)[Symbol.iterator] === "function" || typeof (value as any)[Symbol.asyncIterator] === "function" ) ) { return { async *[Symbol.asyncIterator]() { yield new Result(that.value, that.status) as SafeResult; }, *[Symbol.iterator]() { yield new Result(that.value, that.status) as SafeResult; }, }; } return { async *[Symbol.asyncIterator]() { yield* asyncIterator(value as AsyncGenerator); }, *[Symbol.iterator]() { yield* iterator(value as Generator); }, }; } iter(): IterResult { return this.Iter(); } } type Unwrap = T extends AsyncGenerator ? U : T extends Generator ? U : T extends Promise ? U : T extends (...args: any) => Promise ? U : T extends (...args: any) => infer U ? U : T; type SafeResult = T extends Promise ? Promise>> : T extends () => Promise ? Promise>> : Result>; /** * Grip wraps functions, promises or generators and returns it as a result. * The result can be handled as an object { value, status }, or as a tuple [value, object]. * The result and status interfaces have the methods `ok(), fail(), of(Error)` to check the status: * * ```javascript * const json = grip(response.body.joson()) * if (json.of(SyntaxError)) { * // handle parse error * } * // handle json.value * ``` */ export function grip(action: T): SafeResult { if (action instanceof Promise) { return promise(action) as SafeResult; } try { const result = (action as any)(); if (result instanceof Promise) { return promise(result) as SafeResult; } return new Result(result as T, new Ok()) as SafeResult; } catch (err) { return new Result( null as never, Err.fromCatch(err), ) as SafeResult; } } const promise = async (result: Promise) => { try { return new Result(await result, new Ok()); } catch (err) { return new Result(null as never, Err.fromCatch(err)); } }; const iterator = function* (iter: Generator) { try { let data = iter.next(); while (!data.done) { yield new Result(data.value as T, new Ok()) as SafeResult; data = iter.next(); } } catch (e) { yield new Result(null as never, Err.fromCatch(e)) as SafeResult; } }; const asyncIterator = async function* (iter: AsyncGenerator) { try { let data = await iter.next(); while (!data.done) { yield new Result(data.value as T, new Ok()) as SafeResult; data = await iter.next(); } } catch (e) { yield new Result(null as T, Err.fromCatch(e)) as SafeResult; } };