/** * This module provides enhanced promise handling capabilities beyond native JavaScript Promises. * It focuses on distinguishing between required and optional promises, standardizing results, * and simplifying error handling for complex async operations. */ /** * Symbol used to uniquely identify PromiseResult objects. * This allows for reliable type checking. */ export const PROMISE_RESULT_SYMBOL = Symbol("PromiseResult"); /** * Type guard to check if a value is a PromiseResult. * * @param value - The value to check * @returns True if the value is a PromiseResult, false otherwise */ function isPromiseResult(value) { return value !== null && typeof value === "object" && PROMISE_RESULT_SYMBOL in value; } /** * Custom error class that aggregates multiple promise failures. * Provides access to all underlying errors while presenting a combined error message. */ export class MultiPromiseError extends Error { constructor(reasons) { super(errorMessageForReasons(reasons)); this.reasons = reasons; this.reasons = reasons; } } /** * Helper function to create a standardized fulfilled result object. * Ensures the result has the PROMISE_RESULT_SYMBOL for type checking. * * @param value - The value to wrap in a fulfilled result * @returns A standardized fulfilled result object */ export function makeFulfilledResult(value) { return { success: true, value, error: null, [PROMISE_RESULT_SYMBOL]: true, }; } /** * Helper function to create a standardized rejected result object. * Ensures the result has the PROMISE_RESULT_SYMBOL for type checking. * * @param error - The error to wrap in a rejected result * @returns A standardized rejected result object */ export function makeRejectedResult(error) { const normalizedError = error instanceof Error ? error : new Error(String(error)); return { success: false, value: null, error: normalizedError, [PROMISE_RESULT_SYMBOL]: true, }; } /** * Custom error class that indicates that a required promise failed. */ export class RequiredPromiseError extends Error { constructor(reason) { super(reason.message); this.reason = reason; this.reason = reason; } } /** * Transforms a promise into one that never throws, instead returning a standardized result. * Use this for optional operations that shouldn't cause the overall process to fail. * This is a convenience alias for tryAwait with a more semantic name. * * @param promise - The promise to make optional * @returns A promise that resolves to a PromiseResult instead of throwing */ export async function optional(promise) { return await tryAwait(promise); } /** * Marks a promise as required (will throw on rejection). * This is mainly for code clarity as regular promises are already required by default. * * @param promise - The promise to mark as required * @returns The original promise (identity function) */ export async function required(promise) { return await promise; } /** * Executes a mix of required and optional promises, returning standardized results. * Regular promises are treated as required and will cause this function to throw if they fail. * Promises wrapped with optional() will never cause this function to throw. * * This implementation handles both regular promises and promises that resolve to PromiseResult. * * @param promises - Array of promises (both required and optional) * @returns Promise resolving to an array of standardized results * @throws Original error if a single required promise fails * @throws MultiPromiseError if multiple required promises fail * @note For homogeneous promise arrays, prefer using more specific functions: * - Use allRequired() when all promises should be treated as required * - Use allOptional() when all promises should be treated as optional * - Only use allMixed() when you need to combine both required and optional promises */ export async function allMixed(promises) { // Process each promise const results = await Promise.all(promises.map(async (promise) => { try { const value = await promise; // If it's a PromiseResult (from optional()/tryAwait), use it directly if (isPromiseResult(value)) { // Handle the case where T is already a PromiseResult // This fixes the double-wrapping issue when all promises are optional return value; } else { // It's a regular promise that succeeded return makeFulfilledResult(value); } } catch (error) { // This was a required promise that failed return makeRejectedResult(error instanceof Error ? error : new Error(String(error))); } })); const requiredFailures = []; results.forEach((result) => { if (!result.success) { requiredFailures.push(result.error); } }); // If any required promises failed, throw an error if (requiredFailures.length > 0) { if (requiredFailures.length === 1) { throw requiredFailures[0]; } else { throw new MultiPromiseError(requiredFailures); } } return results; } /** * Executes all promises and returns their values. * All promises are treated as required and will cause this function to throw if any fail. * * @param promises - Array of promises * @returns Promise resolving to an array of values * @throws Original error if a single promise fails * @throws MultiPromiseError if multiple promises fail */ export async function allRequired(promises) { return await Promise.all(promises); } /** * Makes all promises optional and then executes them, returning standardized results. * This function is specifically designed for cases where you want all promises to be treated as optional. * It avoids the double-wrapping issue that can occur when using allMixed with arrays of optional promises. * * @param promises - Array of promises to make optional * @returns Promise resolving to an array of standardized results * @example * ``` * const promises = urls.map(fetchData); * const results = await allOptional(promises); * // Each result is a PromiseResult that won't cause the overall operation to fail * ``` */ export async function allOptional(promises) { // Use Promise.all with optional() for each promise return await Promise.all(promises.map(optional)); } /** * Creates an error message from multiple rejection reasons. * Formats multiple error reasons into a single error message string, * handling both Error objects and other rejection values. * * @param reasons - Array of error reasons that caused promise rejections * @returns Concatenated error message string */ function errorMessageForReasons(reasons) { return reasons .map((reason) => { if (reason instanceof Error) { return reason.message; } else { return JSON.stringify(reason); } }) .join(""); } /** * Safely awaits a promise and returns a standardized result object. * Eliminates the need for repetitive try/catch blocks throughout the codebase. * * @param promiseOrFn - Either a promise or a function that returns a promise * @returns A standardized PromiseResult object indicating success or failure */ export async function tryAwait(promiseOrFn) { try { // Handle both promise and function that returns a promise const promise = typeof promiseOrFn === "function" ? promiseOrFn() : promiseOrFn; const value = await promise; return makeFulfilledResult(value); } catch (error) { return makeRejectedResult(error); } } //# sourceMappingURL=promise-util.js.map