Skip to main content

Overview

Converts an async function that might throw into a function that returns a ResultAsync. This is the safest way to handle third-party async functions.

Signature

static fromThrowable<A extends readonly any[], R, E>(
  fn: (...args: A) => Promise<R>,
  errorFn?: (err: unknown) => E
): (...args: A) => ResultAsync<R, E>
fn
(...args: A) => Promise<R>
required
An async function that might throw or return a rejecting Promise
errorFn
(err: unknown) => E
Optional function to map thrown errors to a known type. Defaults to returning the error as-is.
Returns: A new function with the same parameters that returns ResultAsync<R, E>

Usage

Basic usage

import { ResultAsync } from 'neverthrow'

type DbError = { message: string }

const safeQuery = ResultAsync.fromThrowable(
  async (sql: string) => await db.query(sql),
  (e): DbError => ({ message: String(e) })
)

const result = await safeQuery('SELECT * FROM users')
// result is Result<QueryResult, DbError>

File operations

import fs from 'fs/promises'

const safeReadFile = ResultAsync.fromThrowable(
  fs.readFile,
  (error) => `Failed to read file: ${error}`
)

const content = await safeReadFile('config.json', 'utf-8')
if (content.isOk()) {
  console.log(content.value)
}

API calls

type ApiError = { status: number, message: string }

const safeFetch = ResultAsync.fromThrowable(
  async (url: string) => {
    const response = await fetch(url)
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`)
    }
    return response.json()
  },
  (e): ApiError => ({
    status: 500,
    message: e instanceof Error ? e.message : String(e)
  })
)

const data = await safeFetch('/api/users')

Database operations

interface User {
  id: number
  email: string
}

const findUser = ResultAsync.fromThrowable(
  async (email: string): Promise<User> => {
    const user = await db.users.findUnique({ where: { email } })
    if (!user) throw new Error('User not found')
    return user
  },
  (e) => e instanceof Error ? e.message : 'Unknown error'
)

const user = await findUser('[email protected]')

Why use fromThrowable over fromPromise?

fromThrowable is safer because it catches both synchronous throws and Promise rejections.
// This function can throw synchronously OR return a rejected Promise
const dangerousFunction = async (id: number) => {
  if (id < 0) {
    throw new Error('Invalid ID')  // Synchronous throw!
  }
  return fetch(`/api/${id}`)  // May reject
}

// ❌ UNSAFE - fromPromise doesn't catch synchronous throws
const unsafe = ResultAsync.fromPromise(
  dangerousFunction(-1),  // Throws immediately!
  (e) => String(e)
)

// ✅ SAFE - fromThrowable catches both
const safe = ResultAsync.fromThrowable(
  dangerousFunction,
  (e) => String(e)
)
const result = await safe(-1)  // Returns Err, doesn't throw

Key characteristics

  • Catches all errors: Both sync throws and Promise rejections
  • Preserves parameters: Wrapped function has same signature
  • Type safety: Error type is controlled by errorFn
  • Reusable: Create the wrapper once, use many times

Comparison table

MethodCatches sync throwsCatches async rejectsUse case
fromThrowable✅ Yes✅ YesWrapping functions
fromPromise❌ No✅ YesWrapping Promises
fromSafePromise❌ No❌ NoPromises that never reject

Top-level export

Also available as a standalone function:
import { fromAsyncThrowable } from 'neverthrow'

const safeFn = fromAsyncThrowable(
  myAsyncFunction,
  (e) => `Error: ${e}`
)

Result.fromThrowable

Synchronous version

from-promise

Alternative for Promise objects

Build docs developers (and LLMs) love