Two Tiny Utils for the Result Pattern
12 points by confusedalex
12 points by confusedalex
This looks like a great min/max sweet spot? Much better than many of the other Typescript stabs at this I saw.
Maybe I don’t know enough TypeScript but Promise<Result<T>> looks a bit odd to me - isn’t a Promise effectively an async Result already?
It depends, personally I tend to consider thrown exceptions/rejected promises as panics. In other words, I generally assume a promise should never reject. In this case Promise<Result<T>> makes sense.
I’d prefer wrapping functions, rather than having double function syntax at every function that uses the pattern. It’ll translate to decorators more nicely, too (should even be able to support that transparently). Just porting tryCatch and its examples (with a tentative rename too, and a more precise TypeScript signature for fun):
export function asResult<TArgs extends unknown[], TReturn>(
fn: (...args: TArgs) => TReturn | PromiseLike<TReturn>
) => async (...args: TArgs): Promise<Result<Awaited<TReturn>>> => {
try {
const result = await fn(...args);
return { status: "success", result };
} catch (err: unknown) {
return { status: "error", err };
}
};
export const listPizzas = asResult(api.list);
export const getPizza = asResult(async (id: string): Promise<Pizza> => {
return await api.get(id);
});
I've been considering adding something like this to my projects, wondering if anyone has thoughts on using something like this vs a library like https://github.com/supermacro/neverthrow
If I was going to use a pattern like this, I'd probably use a boolean as the discriminator.
type Result<T> = { ok: true; result: T } | { ok: false; err: unknown };
But this is just fighting the language/ecosystem.