The Problem: Implicit Errors in TypeScript

For developers accustomed to Go's explicit error handling, switching to TypeScript can feel like navigating a minefield. In Go, functions that can fail signal this clearly through their return signature, typically returning a value and an error. The idiomatic `if err != nil` pattern forces developers to acknowledge and handle potential errors immediately where they occur. This contrasts sharply with TypeScript's default approach, which often relies on exceptions. When an error is thrown in TypeScript, it can originate from any layer of the application, and the `catch (e)` block receives an `unknown` type, forcing developers to spend time and effort figuring out what went wrong and where.

This lack of explicit error signaling can lead to subtle bugs and a less predictable control flow. Developers might forget to handle a potential error, or the error itself might be masked by layers of abstraction, making debugging a chore. The need for a more deterministic way to manage errors in asynchronous TypeScript code, similar to Go's reliability, prompted the creation of errval.

Introducing errval: Explicit Errors for TypeScript

Ayman Praxe, a developer who works with both TypeScript and Go, experienced this friction firsthand. The desire to bring Go's clarity to his TypeScript projects led him to build errval. This new library aims to provide a familiar and robust error handling mechanism for JavaScript and TypeScript developers. At its core, errval mirrors Go's convention by allowing functions to return either a successful result or an error, explicitly.

The library is designed to be lightweight and dependency-free. Its minified and gzipped size is a mere 1.86 kB, making it an unobtrusive addition to any project. This small footprint ensures that it won't bloat application bundles, a crucial consideration for performance-sensitive applications and libraries.

Code example demonstrating errval usage with destructuring assignment

How errval Works: Destructuring Assignment

errval leverages JavaScript's destructuring assignment feature to elegantly handle the dual return of a potential error and a successful value. A typical asynchronous operation, like fetching user data, can be wrapped by errval. Instead of returning a promise that resolves with a user object or rejects with an error, the errval-wrapped function returns a promise that resolves with an array. This array contains two elements: the first is an error object (or `null` if successful), and the second is the successful result (or `undefined` if an error occurred).

The syntax is remarkably clean:


const [err, user] = await getUser(id);

if (err) {
  // Handle the error, e.g., return fail(err)
  return fail(err);
}

// If no error, 'user' contains the successful result
console.log(user);

This pattern directly maps to Go's `if err != nil` idiom. Developers can immediately check for the presence of an error after an asynchronous operation completes. If an error exists, it's handled; otherwise, the application proceeds with the successful result. This explicit check reduces the likelihood of unhandled errors and makes the control flow more transparent.

Benefits of errval

The adoption of errval offers several key advantages for TypeScript developers:

  • Improved Error Visibility: Explicit error returns make it clear when a function might fail, forcing developers to consider failure paths.
  • Simplified Control Flow: The `if (err)` check is straightforward and consistent, reducing complexity compared to managing multiple `try...catch` blocks for different error types.
  • Reduced Debugging Time: By handling errors at the point of origin, developers can pinpoint issues more quickly. The `unknown` type from generic exceptions is largely avoided in favor of structured error objects.
  • Lightweight and Dependency-Free: The small size and lack of external dependencies make errval easy to integrate into any project without performance concerns.
  • Familiarity for Go Developers: For those transitioning from Go, errval provides a comfortable and productive error handling paradigm.

Potential Use Cases and Considerations

errval is particularly well-suited for backend TypeScript applications, CLI tools, and any scenario where robust and predictable error handling is paramount. It can be integrated into existing codebases or adopted for new projects. Developers can create utility functions that wrap existing asynchronous operations (like `fetch` or database queries) to conform to the errval pattern.

While errval brings a valuable pattern to TypeScript, it's important to consider how it fits within the broader ecosystem. TypeScript already has built-in support for exceptions. For applications heavily reliant on existing Promise-based error handling or specific error-handling libraries, integrating errval might require careful planning. However, for projects prioritizing explicit error management and seeking a more Go-like development experience, errval presents a compelling solution.

The core idea is to treat errors not as exceptional, but as expected outcomes of operations. This shift in perspective, enabled by tools like errval, can lead to more resilient and maintainable software. The explicit nature of the `[err, result]` pattern ensures that potential failures are never overlooked, making the code safer and easier to reason about.