Skip to content

Safe Results

Set throws: false to get a typed SafeResult<T> discriminated union instead of throwing.

const result = await expectStatus(200, api.getUser(id), {
throws: false,
})
if (result.ok) {
console.log(result.data)
// ^? typed success body
} else {
console.error(result.error.message, result.status)
// ^? number
}
type SafeResult<T> =
| { ok: true; data: T }
| { ok: false; error: Error; status: number; body: unknown }

On success, data is the typed body. On error, you get the Error instance, the raw status, and the raw body.

throws: false works alongside dispatch entries. Handlers and messages still run — but if the final result would be a throw, it’s caught and returned as { ok: false } instead.

const result = await expectStatus(200, response, {
409: ({ orgId }) => redirect(`/org/${orgId}`), // handler can still return
422: 'Please check your input.', // would normally throw
throws: false,
})
if (result.ok) {
// result.data is the success body OR a handler's return value
}
  • Form submissions — show inline errors instead of catching exceptions
  • Data fetching hooks — return { data, error } shapes for UI state
  • Background tasks — log errors without crashing the process
  • Testing — assert on result shape without try/catch boilerplate

recover catches errors and returns a custom fallback. throws: false wraps the entire result in a discriminated union.

// recover — you control the fallback shape
const config = await expectStatus(200, response, {
recover: () => DEFAULT_CONFIG,
})
// throws: false — standard { ok, data, error } shape
const result = await expectStatus(200, response, {
throws: false,
})

Use recover when you want a specific fallback value. Use throws: false when you want the caller to decide what to do with the error.