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";
fn do_something_can_fail() -> Result {
if ... {
return Ok(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 itreturn
instead.This is similar to Rust:
Calling function that can fail
The recommended pattern is
If your
E
type covers falsy values that are valid, use"err" in x
instead 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
tryCatch
to interop with throwing functions, andtryAsync
for async functions.Returning void
Use
Void<E>
as the return type if the function returnsvoid
on successWhy is there no
match
/map
/mapErr
, etc?If you are thinking this is a great idea:
The vanilla
if
doesn't allocate the closures, and has less code, and you can control the flow properly inside the blocks withreturn
/break
/continue
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.