Result type in TypeScript

How to write a simple, generic Result type to hold a value or Error.

Instead of relying on throwing an Error, it’s sometimes more expressive to handle control flow by returning a Result. Here’s how to implement such a construct in TypeScript.

Let’s start with a simple type that can hold a value or Error.

type Result<T> = T | Error;

Now const something: Result<string> = 'something'; is valid, as is const somethingElse: Result<string> = new Error();.

The next step is to differentiate the two at runtime. I can easily check if something is an Error with instanceof, which TypeScript understands as a form of inline type guard.

Caution: You can throw something that isn’t an Error, and this can be an issue if not addressed carefully when creating custom error hierarchies, particularly with transpiled code.

// This works well enough:
if (someResult instanceof Error) {
  handleError(someResult);
}
 
// This is a bit clunky, and it would be easy to omit the parentheses and introduce a bug:
if (!(someResult instanceof Error)) {
  handleSuccess(someResult);
}

To streamline the type check, I’d like to encapsulate it in a function. (Negating the result of a function call is clearer than negating an instanceof check.)

The problem we’ll see is that TypeScript doesn’t propagate any type checks performed in a function into its caller. It will still treat the checked Result as potentially being an Error. Here’s what happens:

const ok = <T>(r: Result<T>) => !(r instanceof Error);
 
if (ok(someResult)) {
  handleSuccess(someResult); // Type error, even though we have a type check inside `ok()`
}

To fix this issue, we can add an explicit type guard to the ok() function, which is written as r is T. This asserts that when the function returns true, the Result that it was called with is the type T, not Error.

const ok = <T>(r: Result<T>): r is T => !(r instanceof Error);
 
if (ok(someResult)) {
  handleSuccess(someResult); // All good here
}

In summary, all we need for a working Result type is the following:

type Result<T> = T | Error;
 
const ok = <T>(r: Result<T>): r is T => !(r instanceof Error);
Related articles

Beyond allowing the await keyword to be used within them, JavaScript’s async functions have their own useful aspects. As an alternative to Promise.resolve() and Promise.reject(), consider using async functions to cleanly wrap return values in Promises for convenient mocks in your test suite and to ensure consistent return types.

2018
JavaScript
TypeScript

Consider using a couple lightweight tools to simplify the process of sharing and enforcing essential JavaScript standards across a project.

2016
Developer tools
JavaScript
TypeScript
Best practices