I once had a fancy error object with TypeScript magic that tries
to reduce allocation while maintaining Result-safety. It turns out
that was slower than allocating plain objects for every return, because
of how V8 optimizes things.
Don't even use isErr() helper functions to abstract. They are slower than
directly property access in my testing.
Function that can fail
Instead of having functions throw, make it return instead.
// Instead of functiondoSomethingCanFail() { if (Math.random() < 0.5) { return42; } throw"oops"; } // Do this importtype { Result } from"pure/result";
fndo_something_can_fail() -> Result<u32, String> { if ... { returnOk(42); }
Err("oops".to_string()) }
Calling function that can fail
The recommended pattern is
constx = doTheCall(); // x is Result<T, E>; if (x.err) { // x.err is E, handle it return ... } // x.val is T // ...
If your E type covers falsy values that are valid, use "err" in x instead of x.err.
A well-known case is Result<T, unknown>. if(r.err) cannot narrow the else case to Ok,
but if("err" in r) can.
Use Void<E> as the return type if the function returns void on success
constx = doSomethingThatVoidsOnSuccess(); if (x.err) { returnx; } // type of x is Record<string, never>, i.e. empty object
Why is there no match/map/mapErr, etc?
If you are thinking this is a great idea:
constresult = foo(bar); match(result, (okValue) => { // handle ok case }, (errValue) => { // handle err case }, );
The vanilla if doesn't allocate the closures, and has less code, and you can
control the flow properly inside the blocks with return/break/continue
constresult = foo(bar); if (result.err) { // handle err case } else { // handle ok case }
As for the other utility functions from Rust's Result type, they really only benefit
because you can early return with ? AND those abstractions are zero-cost in Rust.
Neither is true in JavaScript.
You can also easily write them yourself if you really want to.
I once had a fancy error object with TypeScript magic that tries to reduce allocation while maintaining Result-safety. It turns out that was slower than allocating plain objects for every return, because of how V8 optimizes things.
Don't even use
isErr()helper functions to abstract. They are slower than directly property access in my testing.Function that can fail
Instead of having functions
throw, make itreturninstead.This is similar to Rust:
Calling function that can fail
The recommended pattern is
If your
Etype covers falsy values that are valid, use"err" in xinstead ofx.err. A well-known case isResult<T, unknown>.if(r.err)cannot narrow the else case toOk, butif("err" in r)can.A full example:
Interop with throwing functions
This library also has
tryCatchto interop with throwing functions, andtryAsyncfor async functions.Returning void
Use
Void<E>as the return type if the function returnsvoidon successWhy is there no
match/map/mapErr, etc?If you are thinking this is a great idea:
The vanilla
ifdoesn't allocate the closures, and has less code, and you can control the flow properly inside the blocks withreturn/break/continueAs for the other utility functions from Rust's Result type, they really only benefit because you can early return with
?AND those abstractions are zero-cost in Rust. Neither is true in JavaScript.You can also easily write them yourself if you really want to.