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