Handling Errors
Error types are inferred
Section titled “Error types are inferred”When you create a workflow, TypeScript computes the error union from your dependencies:
const fetchUser = async (id: string): AsyncResult<User, 'NOT_FOUND'> => { ... };const fetchPosts = async (id: string): AsyncResult<Post[], 'FETCH_ERROR'> => { ... };const sendEmail = async (to: string): AsyncResult<void, 'EMAIL_FAILED'> => { ... };
const workflow = createWorkflow('workflow', { fetchUser, fetchPosts, sendEmail });
const result = await workflow.run(async ({ step, deps }) => { ... });// result.error is: 'NOT_FOUND' | 'FETCH_ERROR' | 'EMAIL_FAILED' | UnexpectedErrorAdd a new dependency? The error union updates automatically.
UnexpectedError
Section titled “UnexpectedError”If code throws an exception (not a returned error), it becomes an UnexpectedError (a TaggedError). The original thrown value is in error.cause:
import { isUnexpectedError } from 'awaitly';
const badOperation = async (): AsyncResult<string, 'KNOWN_ERROR'> => { throw new Error('Something broke'); // Throws instead of returning err()};
const workflow = createWorkflow('workflow', { badOperation });const result = await workflow.run(async ({ step, deps }) => { return await step('badOperation', () => badOperation());});
if (!result.ok && isUnexpectedError(result.error)) { console.log(result.error.cause); // The original Error object}Wrapping throwing code
Section titled “Wrapping throwing code”Use step.try to convert thrown exceptions into typed errors:
const result = await workflow.run(async ({ step, deps }) => { const data = await step.try( 'fetchData', async () => { const res = await fetch('/api/data'); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); }, { error: 'FETCH_FAILED' as const } );
return data;});// result.error includes 'FETCH_FAILED'Preserving error details
Section titled “Preserving error details”If your function returns rich error objects, use step.fromResult:
type ApiError = { code: string; message: string };
const callApi = async (): AsyncResult<Data, ApiError> => { return err({ code: 'RATE_LIMITED', message: 'Too many requests' });};
const result = await workflow.run(async ({ step, deps }) => { const data = await step.fromResult( 'callApi', () => callApi(), { onError: (apiError) => ({ type: 'API_ERROR' as const, code: apiError.code, message: apiError.message, }), } ); return data;});Handling specific errors
Section titled “Handling specific errors”Use a switch statement for exhaustive handling:
if (!result.ok) { switch (result.error) { case 'NOT_FOUND': return res.status(404).json({ error: 'User not found' }); case 'UNAUTHORIZED': return res.status(401).json({ error: 'Please log in' }); case 'FETCH_ERROR': return res.status(502).json({ error: 'Upstream service failed' }); default: // UnexpectedError or unknown error console.error(result.error); return res.status(500).json({ error: 'Internal error' }); }}TypeScript ensures you handle all known error cases.
Custom unexpected errors
Section titled “Custom unexpected errors”By default, thrown exceptions become an UnexpectedError. With run(), use ErrorsOf to derive error types from your deps:
import { type ErrorsOf } from 'awaitly';
const deps = { fetchUser, fetchPosts };type RunErrors = ErrorsOf<typeof deps>;
const result = await run<User, RunErrors>(async ({ step }) => { const user = await step('fetchUser', () => fetchUser('1')); const posts = await step('fetchPosts', () => fetchPosts(user.id)); return user;});// result.error is: 'NOT_FOUND' | 'FETCH_ERROR' | UnexpectedErrorTo replace UnexpectedError with a custom type, pass catchUnexpected:
const workflow = createWorkflow('workflow', { fetchUser, fetchPosts }, { catchUnexpected: (thrown) => ({ type: 'UNEXPECTED' as const, message: String(thrown), }), });// result.error is now your workflow errors | { type: 'UNEXPECTED', message: string }When to use string literals vs objects
Section titled “When to use string literals vs objects”| Use case | Recommendation |
|---|---|
| Simple distinct states | String literals: 'NOT_FOUND' | 'UNAUTHORIZED' |
| Errors with context | Objects: { type: 'NOT_FOUND', id: string } |
| API responses | Tagged Errors for structured data |
Need help?
Section titled “Need help?”Having issues with TypeScript narrowing or error handling? See Troubleshooting.