TIL with neverthrow
I've been doing a lot of refactoring into neverthrow recently and hit this surprising case today:
import { ok, err, Result } from "neverthrow";
// Have one function that returns a Result<string, Error>
// value. In this case, the 'ok' type shouldn't matter
// because we never refer to it and only use its
// error type.
function getPerson(): Result<string, Error> {
return err(new Error('Oh no!'))
}
// And then a method that returns a Result<number, Error>
// type, which we will only use the number part from.
function getLength(name: string): Result<number, Error> {
return ok(name.length)
}
// Combine them, and get a surprising result!
function combined() {
const p = getPerson();
if (p.isErr()) return p;
return getLength(p.value);
}
// Even though we only took the error side from
// getPerson, we still have its return type (string)
// lurking in the return type from combined!
const r = combined().map(val => {
val
// ^? (string | number)
})See this code in the TS playground! Basically this is somewhat surprising because I might guess that the .error part of a Result is narrowed to a Result<never, Error>, but I guess it isn't!
The solution was to reconstruct an error type with no ok side:
if (p.isErr()) return err(p.err);I know, this code isn't as functional-programming aesthetic as it could be, but like most things that have users, the Val Town codebase is pragmatic and non-pure.
Overall my feelings on neverthrow so far:
- It's pretty practical and has yielded a big improvement for us.
- The learning curve is a lot less steep than Effect, and the documentation is a lot better, but it's still another thing to learn, and it's easy to in ways that eat a lot of the complexity without capturing the benefit. It is something I've needed to advocate for and help out with quite a bit.
- We use something akin to my proposal of a resultFromAsync function in lots and lots of places. I think that should be part of neverthrow and find it pretty difficult to image living without it.
- We also have a bunch of other utility functions around neverthrow to translate errors to their formats in tRPC, Remix, and Fastify.
- I think that JavaScript/TypeScript‘s poor ergonomics around error handling are hard to fix with an add-on library and I hope a language-standard method arrives eventually.