Result — Modeling Failures
1.51 kB minified + gzipped
A function throws, and nothing in its signature says it can. The failure travels up the call stack until something catches it, usually far from the call that caused it, and the caller’s type says nothing about what went wrong.
Core Behavior
Section titled “Core Behavior”Result<E, A> represents a synchronous operation as either Ok with a value or Err with an
error.
map,chain, andensureskip their callbacks when they receive anErr.chainruns a dependent operation after success and includes its error type in the result.matchhandles both cases;is.okandis.errnarrow the union for ordinaryifstatements.
Reach for Result when the first failure ends the operation. When several independent fields must
each report their own error, Validation collects every failure instead of
stopping at the first. The choosing guide compares the error containers side by
side.
Example: Server Configuration
Section titled “Example: Server Configuration”A server reads its configuration from JSON and checks the host and port before it starts. Each check depends on the previous one succeeding.
chain passes an Ok value to the next check. An existing Err bypasses the check, while the
check can return its own Err:
flowchart TD
Input["Result(configuration)"] --> Status{"Ok?"}
Status -->|Yes| Check["chain calls configuration check"]
Status -->|No| Existing["Existing Err passes through"]
Check -->|Valid| Passed["Ok(checked configuration)"]
Check -->|Invalid| Failed["Err(check error)"]
Passed --> Next["Next check runs"]
Existing --> Skip["Next check is skipped"]
Failed --> Skip
The first returned error stops dependent checks. match can then report that error or use the
successful value.
Parsing Server Configuration
Section titled “Parsing Server Configuration”A direct parser can throw before the caller checks the port. Its return type also does not describe that failure:
Start by converting the parsing exception to a named error. JSON.parse only checks syntax, so its
output still needs validation.
A server configuration also needs a host, a valid port, and a cross-field rule: a public host must not use a privileged port. The pipeline checks these in order. Later checks need the parsed object, so they cannot run when parsing fails.
The error constructors and error-producing callbacks preserve inline literal types. Inline object errors can infer readonly literal properties; choose an explicit wider type if the error must be mutable:
This affects inference, not runtime freezing. TypeScript 5.5 can widen primitive literals returned
by callbacks; the full example uses as const to retain its exact error union on that version.
When the configuration arrives over the network
Section titled “When the configuration arrives over the network”The same parser works when the text comes from a config service instead of a file. Only the source
of the string changes; loadConfig stays intact and runs inside the asynchronous task:
Task.Result keeps the error channel and threads an AbortSignal, so the same typed errors cross
the network boundary. When the request needs retries, timeouts, or overlapping calls, wrap the task
in Op.
Core Operations
Section titled “Core Operations”Creating Values
Section titled “Creating Values”| Operation | What it does | Error or return type |
|---|---|---|
make.ok(value) / make.err(error) |
Wrap a success or an error. | Ok<A> / Err<E> |
tryCatch(operation, { onError }) |
Run synchronous code and map a thrown value. | Result<E, A> |
from.Predicate(predicate, onFalse) |
Check a value; a type guard also narrows it. | Result<E, A> |
from.nullable(onNull) / from.Maybe(onNone) |
Replace a missing value with an error. | Result<E, A> |
from.Validation(combineErrors) |
Convert a validation, combining its errors on failure. | Result<E, A> |
Combining Values
Section titled “Combining Values”| Operation | What it does | Guarantee |
|---|---|---|
chain(next) |
Run a Result-returning operation on an Ok value. |
Preserves the first error; unions error types. |
assignTo(key) / assign(key, next) |
Start a record / add a dependent result to it. | assign skips next on an error. |
struct(fields) |
Combine a record of already-created results. | Returns the first error in own-key enumeration order. |
apply(argument) |
Apply a wrapped function to a wrapped argument. | Prefers the function result’s error if both failed. |
Transforming Values
Section titled “Transforming Values”| Operation | What it does | Guarantee |
|---|---|---|
map(transform) / mapError(transform) |
Transform the success / error value. | The other case passes through. |
mapBoth(onErr, onOk) |
Transform either case. | Only the matching callback runs. |
ensure(predicate, onFail) |
Check an Ok value, optionally narrowing it with a type guard. |
Existing errors pass through. |
tap(effect) / tapError(effect) |
Inspect success / error without replacing the result. | Returns the original result when the callback returns. |
recover(fallback) / recoverUnless(isBlocked, fallback) |
Replace an error with a fallback result. | recoverUnless keeps errors selected by the predicate. |
Running Code and Leaving Result
Section titled “Running Code and Leaving Result”| Operation | What it does | Return type |
|---|---|---|
match({ ok, err }) |
Handle both cases. | The selected handler’s return value. |
getOrElse(fallback) |
Extract success or call a no-argument fallback. | Success or fallback value. |
is.ok(result) / is.err(result) |
Test and narrow the variant. | Boolean type guard. |
to.Maybe(result) / to.Validation(result) |
Convert the outcome. | Maybe discards the error; Validation wraps it in a list. |
sequence.Maybe(result) |
Swap Result<E, Maybe<A>> to Maybe<Result<E, A>>. |
An Err becomes Some(Err); Ok(None) becomes None. |
Failure Behavior and Edge Cases
Section titled “Failure Behavior and Edge Cases”tryCatch catches synchronous throws from its operation, including a JSON.parse syntax error. It
also calls onError inside the catch path; if that callback throws, the exception escapes. Other
callbacks, including predicates, transformations, recovery, and logging, are not wrapped in a catch.
Wrap a throwing operation explicitly where it enters the pipeline.
struct receives results that have already been created: JavaScript evaluates object property
expressions before struct sees them. Its first-error behavior does not prevent those expressions
from running. Use chain or assign to defer a dependent operation until the previous step passes.
For example, both checks below run, even though struct returns the host error:
A promise returned from tryCatch is an Ok containing that promise; a later rejection is not
caught. Use Task.Result or Op for asynchronous work.