Package Exports
Readme
go-errors
A TypeScript library that brings Go-style error handling to JavaScript/TypeScript. Handle errors elegantly without try-catch blocks, using a functional approach that's both type-safe and intuitive.
Features
- 🎯 Type-safe error handling with full TypeScript support
- 🔄 Go-style result tuples
[value, error]
- ⚡ Zero dependencies and lightweight
- 🔀 Unified API for sync, async, and fetch operations
- 🛡️ Predictable error handling without try-catch blocks
- 📦 Tree-shakeable and optimized for bundle size
Installation
npm install go-errors
# or
yarn add go-errors
# or
pnpm add go-errors
# or
bun add go-errors
Quick Start
import { go, goFetch } from 'go-errors';
// Synchronous usage
const [value, error] = go(() => {
// Your code that might throw
return "success";
});
if (error) {
console.error("Something went wrong:", error.message);
} else {
console.log("Got value:", value);
}
// Asynchronous usage
const [data, err] = await go(fetch("https://api.example.com/data"));
if (err) {
console.error("API call failed:", err.message);
} else {
console.log("API data:", data);
}
// Fetch API with type safety
interface UserData {
id: number;
name: string;
}
const [user, fetchError] = await goFetch<UserData>('https://api.example.com/user/1');
if (fetchError) {
console.error("Failed to fetch user:", fetchError);
} else {
console.log("User:", user.name);
}
Why Arrow Functions?
You might wonder why we use go(() => someFunc())
instead of go(someFunc())
. Here's why:
- Error Capturing: Using
go(someFunc())
would executesomeFunc
immediately beforego
can catch any errors. - Delayed Execution: The arrow function ensures the code runs at the right time.
- Context & Arguments: Arrow functions let you pass arguments and maintain context.
// ❌ WRONG: Error throws before go() can catch it
const [result, error] = go(divide(10, 0));
// ✅ CORRECT: Error is properly caught
const [result, error] = go(() => divide(10, 0));
Usage Examples
Basic Error Handling
import { go } from 'go-errors';
function divide(a: number, b: number): number {
if (b === 0) throw new Error("Division by zero");
return a / b;
}
// Synchronous error handling
const [result, err] = go(() => divide(10, 0));
if (err) {
console.log("Failed to divide:", err.message); // "Failed to divide: Division by zero"
} else {
console.log("Result:", result);
}
Async/Promise Handling
import { go } from 'go-errors';
async function fetchUserData(id: string) {
const [response, fetchError] = await go(
fetch(`https://api.example.com/users/${id}`)
);
if (fetchError) {
return [null, fetchError] as const;
}
const [data, parseError] = await go(response.json());
if (parseError) {
return [null, parseError] as const;
}
return [data, null] as const;
}
// Usage
async function main() {
const [userData, error] = await fetchUserData("123");
if (error) {
console.error("Failed to fetch user:", error.message);
return;
}
console.log("User data:", userData);
}
Custom Error Types
import { go } from 'go-errors';
class ValidationError extends Error {
constructor(message: string) {
super(message);
this.name = 'ValidationError';
}
}
function validateUser(user: unknown) {
if (typeof user !== 'object' || !user) {
throw new ValidationError('Invalid user object');
}
return user;
}
const [user, error] = go<unknown, ValidationError>(() =>
validateUser({ name: 'John' })
);
if (error) {
if (error instanceof ValidationError) {
console.log("Validation failed:", error.message);
} else {
console.log("Unknown error:", error.message);
}
}
Working with Multiple Operations
import { go } from 'go-errors';
async function processUserData(userId: string) {
// Fetch user
const [user, userError] = await go(fetchUser(userId));
if (userError) return [null, userError] as const;
// Fetch user's posts
const [posts, postsError] = await go(fetchUserPosts(userId));
if (postsError) return [null, postsError] as const;
// Process everything
return [{
user,
posts,
timestamp: new Date()
}, null] as const;
}
Edge Cases
The library handles various edge cases gracefully:
Circular References: Objects with circular references are handled gracefully with descriptive error messages:
const circular = { foo: 'bar' }; circular.self = circular; const [result, err] = go(() => circular); // err.message will contain information about the circular structure
Special Objects: Special JavaScript objects are properly stringified:
// RegExp const [_, regexErr] = go(() => { throw /test/gi; }); console.log(regexErr.message); // "/test/gi" // Date const [_, dateErr] = go(() => { throw new Date(); }); // Preserves date format in error message
Falsy Values: Properly handles falsy values (undefined, null, 0, false, ''):
const [result, err] = go(() => false); console.log(result === false); // true console.log(err === null); // true
API Reference
Core Functions
go<T, E = Error>(fn: () => T): Result<T, E>
go<T, E = Error>(promise: Promise<T>): Promise<Result<T, E>>
The main function that handles both synchronous and asynchronous operations.
// Sync
const [value, error] = go(() => someOperation());
// Async
const [value, error] = await go(somePromise);
goFetch<T, E = string>(url: string, options?): Promise<Result<T, E>>
A lightweight fetch wrapper that returns a Promise of Result tuple.
// Basic usage
const [data, error] = await goFetch('https://api.example.com/data');
// With type safety and transformers
interface UserData {
id: number;
name: string;
}
const [user, error] = await goFetch<UserData>('https://api.example.com/user/1', {
responseTransformer: (data) => ({
...data,
lastAccessed: new Date()
}),
errorTransformer: (e) => `Custom error: ${e}`
});
Error Handling Patterns
Basic Error Handling
const [result, err] = go(() => {
if (someCondition) throw new Error("Invalid input");
return "success";
});
if (err) {
console.error("Failed:", err.message);
return;
}
console.log("Success:", result);
API Calls with goFetch
interface User {
id: number;
name: string;
email: string;
}
// Simple fetch
const [user, error] = await goFetch<User>('https://api.example.com/user/1');
// With custom error handling
interface ApiError {
code: string;
message: string;
}
const [data, apiError] = await goFetch<User, ApiError>('https://api.example.com/user/1', {
errorTransformer: (e) => ({
code: 'USER_FETCH_ERROR',
message: e instanceof Error ? e.message : 'Unknown error'
})
});
Chaining Operations
async function processUserData(userId: string) {
// Fetch user
const [user, userError] = await goFetch<User>(`/api/users/${userId}`);
if (userError) return [null, userError] as const;
// Process data
const [processed, processError] = go(() => transformUserData(user));
if (processError) return [null, processError] as const;
return [processed, null] as const;
}
Best Practices
Type Your Errors: Always specify error types for better type safety:
const [value, error] = go<number, ValidationError>(() => validate(input));
Early Returns: Use early returns with error checking:
const [data, error] = await fetchData(); if (error) return [null, error] as const;
Error Propagation: Propagate errors up the call stack:
function processData() { const [data, error] = getData(); if (error) return [null, error] as const; // process data }
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature
) - Commit your changes (
git commit -m 'Add some AmazingFeature'
) - Push to the branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Inspired by Go's error handling pattern
- Built with TypeScript for type safety
- Designed for modern JavaScript/TypeScript applications
Made with ❤️ for the TypeScript community