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);