# Promise Error Handling with the to Function
Table of Contents
When working with Promises in JavaScript/TypeScript, error handling can become verbose and repetitive. The to function provides an elegant solution that standardizes error handling and makes your code more readable.
What is the to Function?
The to function is a utility that wraps Promise operations and returns a tuple containing either the result or an error. This pattern is inspired by Go’s error handling approach and eliminates the need for try-catch blocks.
export const to = <T, U = Error & BaseError>( promise: Promise<T>, errorExt?: Record<string, unknown>,) => { return promise .then<[null, T]>((data: T) => [null, data]) .catch< [U, undefined] >((err: U) => [extendError(standardizeError(err), errorExt), undefined])}Basic Usage
Instead of using try-catch blocks, you can use the to function to handle Promise errors:
// Traditional approachasync function fetchUserData(id: string) { try { const response = await fetch(`/api/users/${id}`) const data = await response.json() return data } catch (error) { console.error('Failed to fetch user:', error) throw error }}
// Using the to functionasync function fetchUserDataWithTo(id: string) { const [error, data] = await to(fetch(`/api/users/${id}`).then((res) => res.json()))
if (error) { console.error('Failed to fetch user:', error) return null }
return data}Advanced Examples
1. Database Operations
async function createUser(userData: UserData) { const [error, user] = await to(db.users.create(userData), { operation: 'create_user', userId: userData.id, })
if (error) { logger.error('User creation failed', error) return { success: false, error } }
return { success: true, user }}2. API Calls with Retry Logic
async function fetchWithRetry(url: string, retries = 3) { for (let i = 0; i < retries; i++) { const [error, data] = await to( fetch(url).then((res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`) return res.json() }), { attempt: i + 1, maxRetries: retries }, )
if (!error) return data
if (i === retries - 1) throw error await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1))) }}3. File Operations
import { readFile } from 'fs/promises'
async function readConfigFile(path: string) { const [error, content] = await to(readFile(path, 'utf-8'), { filePath: path, operation: 'read_config', })
if (error) { console.error(`Failed to read config file: ${path}`, error) return null }
try { return JSON.parse(content) } catch (parseError) { console.error('Failed to parse config file', parseError) return null }}Error Standardization
The to function automatically standardizes different types of errors:
// String errorsconst [error1] = await to(Promise.reject('Something went wrong'))// error1 = { message: 'Something went wrong' }
// Number errorsconst [error2] = await to(Promise.reject(404))// error2 = { code: 404, message: 'Error 404' }
// Boolean errorsconst [error3] = await to(Promise.reject(false))// error3 = { success: false, message: 'Failure' }
// Function errorsconst [error4] = await to(Promise.reject(() => {}))// error4 = { message: 'Function error', functionName: '', functionString: '() => {}' }
// Object errorsconst [error5] = await to(Promise.reject({ custom: 'error', code: 500 }))// error5 = { message: 'Object error', custom: 'error', code: 500 }Benefits of Using the to Function
- Consistent Error Handling: All errors are standardized into a predictable format
- No Try-Catch Blocks: Eliminates verbose try-catch syntax
- Type Safety: Full TypeScript support with proper typing
- Error Extension: Ability to add context to errors
- Readable Code: Makes error handling more explicit and readable
Common Use Cases
- API Calls: Handle HTTP requests and responses
- Database Operations: Manage database query errors
- File Operations: Handle file system errors
- Third-party Library Integration: Wrap external library calls
- Async Operations: Any Promise-based operation
Best Practices
- Always Check for Errors: Always destructure the result and check for errors
- Add Context: Use the
errorExtparameter to add relevant context - Log Errors: Implement proper logging for debugging
- Handle Gracefully: Provide fallback values or user-friendly error messages
- Type Your Errors: Define proper error types for better type safety
interface ApiError extends Error { statusCode?: number endpoint?: string}
async function fetchUserProfile(userId: string) { const [error, user] = await to<User, ApiError>( fetch(`/api/users/${userId}`).then((res) => res.json()), { endpoint: `/api/users/${userId}`, userId }, )
if (error) { // Log with context logger.error('Failed to fetch user profile', { userId, endpoint: error.endpoint, message: error.message, })
// Return fallback or throw return null }
return user}The to function is a powerful utility that can significantly improve your Promise error handling experience. By standardizing error formats and eliminating try-catch boilerplate, it makes your code more maintainable and easier to debug.
source code implementation
const errorStrategies = { string: (value: string) => ({ message: value }), number: (value: number) => ({ code: value, message: `Error ${value}` }), boolean: (value: boolean) => ({ success: value, message: value ? 'Success' : 'Failure', }), function: (value: Function) => ({ message: 'Function error', functionName: value.name, functionString: value.toString(), }), object: (value: object) => ({ message: 'Object error', ...value, }), default: (value: unknown) => ({ message: String(value), originalValue: value, }),}
const typeGuards = { isEmpty: (value: unknown): value is undefined | null | '' => value === undefined || value === null || value === '', isError: (value: unknown): value is Error => value instanceof Error, isObject: (value: unknown): value is object => typeof value === 'object' && value !== null, isString: (value: unknown): value is string => typeof value === 'string', isNumber: (value: unknown): value is number => typeof value === 'number', isBoolean: (value: unknown): value is boolean => typeof value === 'boolean', isFunction: (value: unknown): value is Function => typeof value === 'function',} as const
const standardizeError = <U>(err: U): U => { const toErrorObject = (error: Error) => ({ message: error.message, name: error.name, stack: error.stack, })
// 使用策略模式处理不同类型的错误 const processError = (value: unknown): Record<string, unknown> => { if (typeGuards.isString(value)) return errorStrategies.string(value) if (typeGuards.isNumber(value)) return errorStrategies.number(value) if (typeGuards.isBoolean(value)) return errorStrategies.boolean(value) if (typeGuards.isFunction(value)) return errorStrategies.function(value) if (typeGuards.isObject(value)) return errorStrategies.object(value) return errorStrategies.default(value) }
return ( typeGuards.isEmpty(err) ? toErrorObject(new Error('Empty error')) : typeGuards.isError(err) ? toErrorObject(err) : processError(err) ) as U}
type BaseError = { [key: string]: unknown}
// 合并错误扩展const extendError = <U>(err: U, errorExt?: Record<string, unknown>): U => errorExt ? ({ ...err, ...errorExt } as U) : err
export const to = <T, U = Error & BaseError>( promise: Promise<T>, errorExt?: Record<string, unknown>,) => { return promise .then<[null, T]>((data: T) => [null, data]) .catch< [U, undefined] >((err: U) => [extendError(standardizeError(err), errorExt), undefined])}