TypeScript Result Type for Error Handling
The `typescript-result` package provides a robust, type-safe `Result` type, inspired by Rust's `Result` enum, for managing operations that can succeed or fail without relying on traditional `try-catch` blocks or throwing exceptions. It aims to transform chaotic error handling into elegant, functional code by enforcing explicit error handling at compile time. The current stable version is 3.5.2, with active development indicated by recent beta releases (e.g., v3.6.0-beta.3) that frequently introduce new features and fixes. Key differentiators include explicit `Ok` and `Err` variants, methods like `isOk()`, `isError()`, `map()`, `mapErr()`, and the recently added `match()` for basic pattern matching on error types. It targets modern Node.js environments (`>=18`) and is designed for seamless integration into TypeScript projects, improving code predictability and maintainability compared to implicit exception propagation.
Common errors
-
Property 'value' does not exist on type 'Err<any>'
cause Attempting to access the `value` property on a `Result` that is an `Err` variant without first checking its type.fixAlways use `if (result.isOk()) { /* access result.value */ }` or other safe methods (`map`, `andThen`, `match`) to ensure you are operating on an `Ok` variant. -
TypeError: Cannot read properties of undefined (reading 'unwrap')
cause This typically occurs in JavaScript (runtime) when `unwrap()` is called on a `Result` instance that is actually `undefined` or `null`, often due to a preceding operation failing silently or not returning a `Result` as expected.fixEnsure that the variable you are calling `unwrap()` on is indeed a `Result` instance. In TypeScript, this is usually caught at compile-time. If it's a valid `Result` but `Err`, the error will be `Error: Called unwrap() on an Err value`. -
Error: Called unwrap() on an Err value
cause The `unwrap()` method was invoked on a `Result` instance that contained an error (`Err` variant). This is by design to explicitly throw if an error is unhandled.fixRefactor your code to explicitly handle the `Err` case using `isOk()`, `mapErr()`, `orElse()`, or `match()` before attempting to access the successful `value`. Only use `unwrap()` when you are absolutely certain the `Result` will always be `Ok` (e.g., after validation). -
Error: [ERR_REQUIRE_ESM]: require() of ES Module ... not supported. Instead change the require of ... to a dynamic import()
cause Attempting to import `typescript-result` using CommonJS `require()` syntax in an environment configured for ES Modules, or when the library itself is primarily ESM.fixSwitch to ES Module import syntax: `import { Result } from 'typescript-result';`. Ensure your project's `package.json` has `"type": "module"` or use a bundler that transpiles correctly.
Warnings
- gotcha Calling `unwrap()` or `expect()` on an `Err` result will throw a runtime error, bypassing the type-safety benefits of the Result type. Always check `isOk()` or use safe methods like `mapErr()`, `andThen()`, `orElse()`, or `match()` to handle the error variant explicitly.
- breaking Prior to v3.5.2, the static methods `Result.try` and `Result.fromAsyncCatching` might not have caught exceptions thrown *inside* their error transformation callbacks (the `errorFn` argument). This could lead to uncaught exceptions escaping the `Result`'s handling mechanism.
- gotcha Before v3.4.1, using generator syntax (`yield*`, `async function*`) with `Result` types could fail due to an incorrect `@internal` annotation on the `[Symbol.iterator]()` method, preventing proper iteration.
- gotcha Prior to v3.5.1, TypeScript's inference engine could struggle with `Result` types when the underlying success `value` had a structural shape that overlapped with the `Result` type itself (e.g., an object with an `ok` or `error` property). This could lead to incorrect type narrowing.
Install
-
npm install typescript-result -
yarn add typescript-result -
pnpm add typescript-result
Imports
- Result
const { Result, Ok, Err } = require('typescript-result')import { Result, Ok, Err } from 'typescript-result' - Ok
import { ok } from 'typescript-result'import { Ok } from 'typescript-result' - fromAsyncCatching
import { fromAsyncCatching } from 'typescript-result'import { Result } from 'typescript-result'; const asyncOp = Result.fromAsyncCatching(async () => {...});
Quickstart
import { Result, Ok, Err } from 'typescript-result';
interface User { id: string; name: string; }
// Simulate a database operation that might fail
async function getUserById(id: string): Promise<Result<User, Error>> {
return new Promise(resolve => {
setTimeout(() => {
if (id === '123') {
resolve(Ok({ id: '123', name: 'Alice' }));
} else if (id === 'error') {
resolve(Err(new Error('Database error: Failed to fetch user.')));
} else {
resolve(Err(new Error('User not found.')));
}
}, 100);
});
}
async function processUser(userId: string) {
const userResult = await getUserById(userId);
if (userResult.isOk()) {
console.log(`Successfully fetched user: ${userResult.value.name}`);
// Further processing with userResult.value
} else {
console.error(`Failed to fetch user: ${userResult.error.message}`);
// Handle specific errors using match() available since v3.5.0
userResult
.match()
.when(Error, (err) => {
if (err.message.includes('not found')) {
console.warn('User ID not recognized.');
} else {
console.error(`Unhandled error type: ${err.message}`);
}
})
.run();
}
}
processUser('123'); // Success
processUser('456'); // User not found
processUser('error'); // Database error