DEV Community

Larson
Larson

Posted on • Edited on

value, err := in javascript

Embracing Error Handling in TypeScript: A Journey Beyond Try-Catch

As a software developer continually exploring best practices in TypeScript, I've recently ventured into an intriguing approach to error handling: leveraging value types. This method, while somewhat unconventional, offers a refreshing alternative to the traditional try-catch paradigm.

The Traditional Try-Catch: Not Always the Catch-All Solution

In the conventional landscape of TypeScript error handling, the try-catch block is ubiquitous. However, it often falls short in practice. Consider the following typical pattern:

async myFunction() {
    try {
        const res = await fetch();
        const json = await res.json();
        // Additional processing
    } catch (e) {
        // Exception handling (sometimes absent)
    }
}
Enter fullscreen mode Exit fullscreen mode

This approach, while functional, masks the granular detail of where exactly an error occurs. Debugging becomes a game of hide-and-seek with errors, demanding meticulous inspection of each operation within the try block.

A More Granular Approach: Individual Try-Catch Blocks

To pinpoint the source of errors, one might be tempted to wrap each operation in its own try-catch block:

async myFunction() {
    try {
        const res = await fetch();
    } catch (e) {
        // Error handling
    }

    try {
        const json = await res.json();
    } catch (e) {
        // Error handling
    }

    // Further operations with individual try-catch blocks
}
Enter fullscreen mode Exit fullscreen mode

While this method increases error location precision, it quickly bloats the code with repetitive structures, hindering readability and maintainability.

Leveraging Value Types for Error Handling

In search of a more elegant solution, I experimented with a pattern that utilizes value types for error handling:

myFunction() {
    try {
        // Perform operations
        return ["Hello, World!", null];
    } catch (e) {
        return [null, e.message];
    }
}

const [value, err] = myFunction();
if (err != null) {
    // Handle error
}
Enter fullscreen mode Exit fullscreen mode

This pattern enforces handling errors at the function where they occur, promoting clearer error management and streamlined code.

Integrating Custom Linting and Third-Party Library Wrappers

To ensure robustness in this approach, I integrated custom linting rules that validate the return values, ensuring that the error handling conforms to the desired pattern. Additionally, for third-party library integration, I developed a wrapper, etov, which simplifies the process:

import etov from 'etov';
import someThirdPartyFunction from 'some-third-party-library';

const [result, error] = etov(someThirdPartyFunction, arg1, arg2);
if (error) {
    console.error('An error occurred:', error);
} else {
    console.log('Third-party function result:', result);
}
Enter fullscreen mode Exit fullscreen mode

Performance Considerations: A Balancing Act

Admittedly, I'm still evaluating the performance implications and the garbage collector's behavior with the frequent array returns. Nonetheless, for my current applications, this method proves effective and enhances my coding experience.

Conclusion

In wrapping up, this dive into value-based error handling is much more than a coding experiment – it's a reflection of my passion of efficient, and human-readable code in TypeScript. I've always been a fan of handling errors as values; there's something elegantly simple about it that resonates with my coding philosophy. Moving away from the cumbersome bloat of traditional try-catch blocks, this approach not only streamlines error handling but also aligns with my affinity for writing code that's as understandable to humans as it is to machines.

Happy coding, and may your error logs be short and your coffee cup always be full! ☕🚀

Top comments (0)