Core API

safe.sync

Executes a synchronous function and returns a SafeResult tuple.


Signatures

safe.sync<T>(fn: () => T): SafeResult<T, Error>
safe.sync<T>(fn: () => T, hooks: SafeHooks<T, Error, []>): SafeResult<T, Error>
safe.sync<T, E>(fn: () => T, parseError: (e: unknown) => NonFalsy<E>): SafeResult<T, E>
safe.sync<T, E>(fn: () => T, parseError: (e: unknown) => NonFalsy<E>, hooks: SafeHooks<T, E, []> & { defaultError: E }): SafeResult<T, E>

Basic usage

const [result, error] = safe.sync(() => JSON.parse('{"name": "John"}'))

if (error) {
  console.error('Parse failed:', error.message)
  return
}
console.log(result) // { name: "John" }

On success, result contains the return value and error is null. On failure, result is null and error is the caught Error.

Error normalization

When no parseError is provided, non-Error thrown values (strings, numbers, etc.) are automatically normalized to Error instances via new Error(String(e)). The original thrown value is preserved as error.cause. This ensures the default SafeResult<T, Error> type is always truthful.


With error mapping

Transform the caught error into a custom type using the parseError parameter:

type ParseError = { code: string; message: string }

const [result, error] = safe.sync(
  () => JSON.parse(invalidJson),
  (e): ParseError => ({
    code: 'PARSE_ERROR',
    message: e instanceof Error ? e.message : 'Unknown error',
  })
)

if (error) {
  console.error(error.code, error.message) // error is typed as ParseError
}

With hooks

Hooks let you execute side effects (logging, analytics, error reporting) without affecting the return value:

const [result, error] = safe.sync(() => computeExpensiveValue(), {
  onSuccess: (value, []) => console.log('Computed:', value),
  onError: (error, []) => reportToSentry(error),
})

The second parameter in each hook is the context tuple. For safe.sync, this is always an empty tuple [].


With error mapping and hooks

Combine both for full control:

const [result, error] = safe.sync(
  () => JSON.parse(jsonString),
  (e): ParseError => ({ code: 'PARSE_ERROR', message: String(e) }),
  {
    onSuccess: (data, []) => analytics.track('parse_success'),
    onError: (error, []) =>
      analytics.track('parse_failed', { code: error.code }),
  }
)

Hook execution

Hooks run after the operation completes but before the result is returned. They do not affect the return value.

Previous
safe.wrapAsync