UNPKG

@ayonli/jsext

Version:

A JavaScript extension package for building strong and modern applications.

325 lines (324 loc) 10.4 kB
/** * This module provides the {@link Result} type that represents the result of * an operation that may succeed or fail, similar to the `Result` type in Rust, * but with a more JavaScript-friendly API and removes unnecessary boilerplate. * * This module provides a {@link try_} function (alias of {@link Result.try}) to * safely invoke a function that may throw and wraps the result in a `Result` * object. * * This module also provides the {@link Ok} and {@link Err} functions as * shorthands for constructing successful and failed results, respectively. Also, * a {@link Result.wrap} function is provided to wrap a traditional function * that may throw an error into a function that always returns a `Result` object. * * This module don't give up the error propagation mechanism in JavaScript, we * can still use the `try...catch` statement to catch the error and handle it in * a traditional way. Which is done by using the `unwrap()` method of the * `Result` object to retrieve the value directly. * * @example * ```ts * // Safely invoke a function that may throw with `try_`. * import { try_ } from "@ayonli/jsext/result"; * * function divide(a: number, b: number): number { * if (b === 0) { * throw new RangeError("division by zero"); * } * * return a / b; * } * * const result = try_<number, RangeError>(() => divide(10, 0)); * if (result.ok) { * console.log("Result:", result.value); * } else { * console.error("Error:", result.error.message); * } * ``` * * @example * ```ts * // Wrap a traditional function that may throw with `Result.wrap`. * import { Err, OK, Result } from "@ayonli/jsext/result"; * * function divide(a: number, b: number): number { * if (b === 0) { * throw new RangeError("division by zero"); * } * * return a / b; * } * * const safeDivide = Result.wrap(divide); * * const result = safeDivide(10, 0); * if (result.ok) { * console.log("Result:", result.value); * } else { * console.error("Error:", result.error.message); * } * ``` * * @example * ```ts * // Define a class method that always returns a `Result` object. * import { Err, OK, Result } from "@ayonli/jsext/result"; * * class Calculator { * \@Result.wrap() * divide(a: number, b: number): Result<number, RangeError> { * if (b === 0) { * return Err(new RangeError("division by zero")); * } * * return Ok(a / b); * } * } * * const calc = new Calculator(); * * const result = calc.divide(10, 0); * if (result.ok) { * console.log("Result:", result.value); * } else { * console.error("Error:", result.error.message); * } * ``` * * @example * ```ts * // Use the `unwrap()` method to retrieve the value directly. * import { Err, OK, Result } from "@ayonli/jsext/result"; * * function safeDivide(a: number, b: number): Result<number, RangeError> { * if (b === 0) { * return Err(new RangeError("division by zero")); * } * * return Ok(a / b); * } * * try { * const value = safeDivide(10, 0).unwrap(); * console.log("Result:", value); * } catch (error) { * console.error("Error:", (error as RangeError).message); * } * ``` * * @experimental * @module */ import { MethodDecorator } from "./types.ts"; export interface IResult<T, E> { /** * Whether the result is successful. */ readonly ok: boolean; /** * The value of the result if `ok` is `true`. */ value?: T | undefined; /** * The error of the result if `ok` is `false`. */ error?: E | undefined; /** * Returns the value if the result is successful, otherwise throws the error, * unless the error is handled and a fallback value is provided. * * @param onError Handles the error and provides a fallback value. * * * @example * ```ts * import { Err, OK, Result } from "@ayonli/jsext/result"; * * function safeDivide(a: number, b: number): Result<number, RangeError> { * if (b === 0) { * return Err(new RangeError("division by zero")); * } * * return Ok(a / b); * } * * try { * const value = safeDivide(10, 0).unwrap(); * console.log("Result:", value); * } catch (error) { * console.error("Error:", (error as RangeError).message); * } * ``` */ unwrap(onError?: ((error: E) => T) | undefined): T; /** * Returns the value if the result is successful, otherwise returns * `undefined`. */ optional(): T | undefined; } export interface ResultOk<T, E> extends IResult<T, E> { readonly ok: true; readonly value: T; readonly error?: undefined; } export interface ResultErr<T, E> extends IResult<T, E> { readonly ok: false; readonly error: E; readonly value?: undefined; } export interface ResultStatic { /** * Safely invokes a function that may throw and wraps the result in a `Result` * object. If the returned value is already a `Result` object, it will be * returned as is. * * @example * ```ts * import { try_ } from "@ayonli/jsext/result"; * * function divide(a: number, b: number): number { * if (b === 0) { * throw new RangeError("division by zero"); * } * * return a / b; * } * * const result = try_<number, RangeError>(() => divide(10, 0)); * if (result.ok) { * console.log("Result:", result.value); * } else { * console.error("Error:", result.error.message); * } * ``` */ try<T, E = unknown, A extends any[] = any[]>(fn: (...args: A) => Result<T, E>, ...args: A): Result<T, E>; try<T, E = unknown, A extends any[] = any[]>(fn: (...args: A) => Promise<Result<T, E>>, ...args: A): Promise<Result<T, E>>; try<R, E = unknown, A extends any[] = any[]>(fn: (...args: A) => Promise<R | never>, ...args: A): Promise<Result<R, E>>; try<R, E = unknown, A extends any[] = any[]>(fn: (...args: A) => R | never, ...args: A): Result<R, E>; /** * Resolves the promise's result in a `Result` object. If the fulfilled value * is already a `Result` object, it will be resolved as is. */ try<T, E = unknown>(promise: Promise<Result<T, E>>): Promise<Result<T, E>>; try<R, E = unknown>(promise: Promise<R | never>): Promise<Result<R, E>>; /** * Wraps a function that may throw and returns a new function that always * returns a `Result` object. If the returned value is already a `Result` * object, it will be returned as is. * * @example * ```ts * import { Err, OK, Result } from "@ayonli/jsext/result"; * * function divide(a: number, b: number): number { * if (b === 0) { * throw new RangeError("division by zero"); * } * * return a / b; * } * * const safeDivide = Result.wrap(divide); * * const result = safeDivide(10, 0); * if (result.ok) { * console.log("Result:", result.value); * } else { * console.error("Error:", result.error.message); * } * ``` */ wrap<A extends any[], T, E = unknown>(fn: (...args: A) => Result<T, E>): (...args: A) => Result<T, E>; wrap<A extends any[], T, E = unknown>(fn: (...args: A) => Promise<Result<T, E>>): (...args: A) => Promise<Result<T, E>>; wrap<A extends any[], R, E = unknown>(fn: (...args: A) => Promise<R | never>): (...args: A) => Promise<Result<R, E>>; wrap<A extends any[], R, E = unknown>(fn: (...args: A) => R | never): (...args: A) => Result<R, E>; /** * Used as a decorator, make sure a method always returns a `Result` * instance, even if it throws. * * @example * ```ts * import { Err, OK, Result } from "@ayonli/jsext/result"; * * class Calculator { * \@Result.wrap() * divide(a: number, b: number): Result<number, RangeError> { * if (b === 0) { * return Err(new RangeError("division by zero")); * } * * return Ok(a / b); * } * } * * const calc = new Calculator(); * * const result = calc.divide(10, 0); * if (result.ok) { * console.log("Result:", result.value); * } else { * console.error("Error:", result.error.message); * } * ``` */ wrap(): MethodDecorator; } export type ResultLike<T, E = unknown> = { ok: true; value: T; error?: undefined; } | { ok: false; error: E; value?: undefined; }; /** * Represents the result of an operation that may succeed or fail. */ export type Result<T, E = unknown> = ResultOk<T, E> | ResultErr<T, E>; export declare const Result: { new <T, E = unknown>(init: ResultLike<T, E>): Result<T, E>; prototype: Result<unknown, unknown>; } & ResultStatic; /** * Constructs a successful result with the given value. */ export declare const Ok: <T, E = unknown>(value: T) => Result<T, E>; /** * Constructs a failed result with the given error. */ export declare const Err: <E, T = never>(error: E) => Result<T, E>; /** * A alias for {@link Result.try}. * * @example * ```ts * import { try_ } from "@ayonli/jsext/result"; * * function divide(a: number, b: number): number { * if (b === 0) { * throw new RangeError("division by zero"); * } * * return a / b; * } * * const result = try_<number, RangeError>(() => divide(10, 0)); * if (result.ok) { * console.log("Result:", result.value); * } else { * console.error("Error:", result.error.message); * } * ``` */ export declare const try_: { <T, E = unknown, A extends any[] = any[]>(fn: (...args: A) => Result<T, E>, ...args: A): Result<T, E>; <T_1, E_1 = unknown, A_1 extends any[] = any[]>(fn: (...args: A_1) => Promise<Result<T_1, E_1>>, ...args: A_1): Promise<Result<T_1, E_1>>; <R, E_2 = unknown, A_2 extends any[] = any[]>(fn: (...args: A_2) => Promise<R | never>, ...args: A_2): Promise<Result<R, E_2>>; <R_1, E_3 = unknown, A_3 extends any[] = any[]>(fn: (...args: A_3) => R_1, ...args: A_3): Result<R_1, E_3>; <T_2, E_4 = unknown>(promise: Promise<Result<T_2, E_4>>): Promise<Result<T_2, E_4>>; <R_2, E_5 = unknown>(promise: Promise<R_2>): Promise<Result<R_2, E_5>>; };