JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • 0
  • Score
    100M100P100Q43491F
  • License MIT

Go-style error handling for TypeScript with full type safety, async, and fetch support

Package Exports

    Readme

    go-errors

    npm version TypeScript License: MIT

    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:

    1. Error Capturing: Using go(someFunc()) would execute someFunc immediately before go can catch any errors.
    2. Delayed Execution: The arrow function ensures the code runs at the right time.
    3. 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:

    1. 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
    2. 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
    3. 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

    1. Type Your Errors: Always specify error types for better type safety:

      const [value, error] = go<number, ValidationError>(() => validate(input));
    2. Early Returns: Use early returns with error checking:

      const [data, error] = await fetchData();
      if (error) return [null, error] as const;
    3. 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.

    1. Fork the repository
    2. Create your feature branch (git checkout -b feature/AmazingFeature)
    3. Commit your changes (git commit -m 'Add some AmazingFeature')
    4. Push to the branch (git push origin feature/AmazingFeature)
    5. 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