logo hsb.horse
← Back to snippets index

Snippets

Implementing a Lightweight Result Type in TypeScript

An error handling pattern without try-catch. Type-safe error handling with a Result type that separates data and error.

Published: Updated:

Error handling for asynchronous operations is often written with try-catch, but the location where exceptions occur and the location where they are handled tend to diverge. With the Result type pattern, you can explicitly separate success and failure as return values.

Prerequisite: Conversion to Error Type

A helper to reliably convert unknown type errors into Error type.

export function toError(error: unknown): Error {
if (error instanceof Error) return error;
return new Error(error as never);
}

Definition of Result Type

interface Failure {
data: undefined;
error: Error;
}
interface Success<T> {
data: T;
error: undefined;
}
type Result<T> = Success<T> | Failure;

Example Implementation of fetch Wrapper

import { toError } from './toError.js'
export async function fetcher<T>(
url: RequestInfo,
init?: RequestInit
): Promise<Result<T>> {
const UNDEF = undefined;
// When fetch fails (network errors, etc.)
const response = await fetch(url, init).catch(toError);
if (response instanceof Error) {
return { data: UNDEF, error: response };
}
// HTTP error responses
if (!response.ok) {
return {
data: UNDEF,
error: new Error(`HTTP ${response.status}: ${response.statusText}`)
};
}
// Success
const data = await response.json();
return { data, error: UNDEF };
}

Usage Example

const result = await fetcher<User>("/api/user/123");
if (result.error) {
console.error("Failed to fetch:", result.error.message);
return;
}
// When result.error is undefined, TypeScript infers result.data as T
console.log(result.data.name);

Benefits and Considerations

Benefits:

  • Success and failure become clear from the return type
  • Reduced try-catch blocks improve readability
  • Error handling can be enforced (omission causes type errors)

Considerations:

  • If the ECMAScript Safe Assignment Operator proposal is standardized, built-in similar functionality may become available
  • Not as complete as Go or Rust’s language built-in Result types
  • Detailed error handling such as validation errors requires separate implementation

The implementation is simple, but it’s effective when you want to handle errors type-safely in small-scale projects.