Skip to content
VersionSize

attempt

The attempt utility safely executes a function and returns a tuple containing either the result or the error. It follows the "Go-style" error handling pattern, allowing you to handle failures without try/catch blocks.

Implementation

View Source Code
ts
import { Logit } from '@vielzeug/logit';
import type { Fn } from '../types';
import { predict } from './predict';
import { retry } from './retry';

type AttemptOptions = {
  identifier?: string;
  retries?: number;
  silent?: boolean;
  timeout?: number;
};

/**
 * Attempts to execute a function with advanced error handling and retry logic.
 *
 * @example
 * ```ts
 * const unreliableFunction = async () => {
 *   if (Math.random() < 0.7) throw new Error ('Random failure');
 *   return 'Success!';
 * };
 *
 * await attempt(
 *   unreliableFunction,
 *   { retries: 3, silent: false, timeout: 5000 }); // Success! (or undefined if all attempts failed)
 * ```
 *
 * @param fn - The function to be executed.
 * @param [options] - Configuration options for the attempt.
 * @param [options.identifier] - Custom identifier for logging purposes.
 * @param [options.retries=0] - Number of retry attempts if the function fails.
 * @param [options.silent=false] - If true, suppresses error logging.
 * @param [options.timeout=7000] - Timeout in milliseconds for function execution.
 *
 * @returns The result of the function or undefined if it failed.
 */
export async function attempt<T extends Fn, R = Awaited<ReturnType<T>>>(
  fn: T,
  { silent = false, retries = 0, timeout = 7000, identifier = fn.name || 'anonymous function' }: AttemptOptions = {},
): Promise<R | undefined> {
  try {
    return await retry(() => predict<R>(() => fn(), { timeout }), { times: retries + 1 });
  } catch (err) {
    if (!silent) {
      Logit.error(`attempt(${identifier}) -> all attempts failed`, { cause: err });
    }
    return undefined;
  }
}

Features

  • Isomorphic: Works in both Browser and Node.js.
  • Async Support: Automatically handles Promises. If the input function returns a Promise, attempt returns a Promise resolving to the result/error tuple.
  • Clean Syntax: Encourages a consistent, flat error-handling style across your codebase.
  • Type-safe: Correctly typed result and error positions.

API

ts
type AttemptResult<T> = [T, null] | [null, any];

function attempt<T>(fn: () => T | Promise<T>): Promise<AttemptResult<T>>;

Parameters

  • fn: The function to execute. Can be synchronous or asynchronous.

Returns

  • A tuple: [result, null] on success, or [null, error] on failure.
  • Returns a Promise resolving to this tuple if fn is asynchronous.

Examples

Synchronous Error Handling

ts
import { attempt } from '@vielzeug/toolkit';

const [data, error] = attempt(() => JSON.parse('invalid json'));

if (error) {
  console.error('Failed to parse:', error.message);
} else {
  console.log('Parsed data:', data);
}

Asynchronous Operations

ts
import { attempt } from '@vielzeug/toolkit';

const [user, fetchError] = await attempt(() => fetch('/api/user/1').then((r) => r.json()));

if (fetchError) {
  handleError(fetchError);
}

Implementation Notes

  • Performance-optimized with minimal overhead compared to a raw try/catch.
  • If the argument fn is not a function, it returns [null, TypeError] (depending on implementation, usually it expects a function).
  • Works perfectly with await for a clean, readable async flow.

See Also

  • retry: Automatically re-run logic that might fail.
  • assert: Throw errors when conditions are not met.
  • parseJSON: Specialized safe JSON parsing.