Appearance
Result Type
When to use
Use this pattern when a function can fail and you want to represent success or failure as a value rather than throwing an exception. The pattern makes error handling explicit at the call site, prevents unhandled exceptions from propagating, and works well for operations like parsing, validation, or I/O where failure is expected.
The pattern
A frozen Result class with two constructors — ok() for success and err() for failure — plus a try() helper that wraps a function call and catches any thrown error.
Basic usage
Result.try() wraps a function call in a try/catch and returns a Result instead of throwing. The caller inspects .ok to determine success or failure, and .value or .error to access the outcome:
javascript
function parseJSON(text) {
return Result.try(() => JSON.parse(text));
}
const good = parseJSON('{"name": "Alice"}');
good.ok; // true
good.value; // { name: "Alice" }
good.error; // undefined
const bad = parseJSON("not json");
bad.ok; // false
bad.value; // undefined
bad.error; // SyntaxError: ...Branching on result
Check .ok to branch on success or failure. On success, .value holds the return value; on failure, .error holds the caught exception:
javascript
const result = parseJSON(input);
if (result.ok) {
process(result.value);
} else {
console.error("Parse failed:", result.error.message);
}Explicit construction
Use Result.ok() and Result.err() directly when the logic doesn't throw:
javascript
function validateAge(age) {
if (typeof age !== "number" || age < 0) {
return Result.err(
new TypeError("age must be a non-negative number")
);
}
return Result.ok(age);
}Frozen instances
Every Result is frozen after construction. The ok, value, and error properties cannot be reassigned:
javascript
const r = Result.ok(42);
r.ok = false; // silently ignored (frozen)
r.ok; // trueImplementing Result
The class freezes each instance after construction so that ok, value, and error are immutable. Result.ok() and Result.err() are static factories, and Result.try() wraps a throwable function in a try/catch that returns a Result instead of propagating the exception.
javascript
export class Result {
constructor(ok, value, error) {
Object.assign(this, {
ok: Boolean(ok),
value,
error,
});
return Object.freeze(this);
}
static ok(value) {
return new Result(true, value);
}
static err(error) {
return new Result(false, undefined, error);
}
static try(fn) {
try {
return Result.ok(fn());
} catch (error) {
return Result.err(error);
}
}
}Trade-offs
- Pros: Makes error handling explicit — callers must check
.okbefore using.value. Frozen instances prevent accidental mutation.Result.try()wraps throwable code without manual try/catch at every call site. - Cons: Adds a wrapper object around every return value. Callers must remember to check
.ok— unlike exceptions, nothing forces you to handle the error. - Versus throwing: Use
Resultwhen failure is a normal, expected outcome (parsing, validation, lookups). Usethrowfor truly exceptional conditions (programmer errors, invariant violations). - Versus nullable returns:
Resultcarries the error context. Returningnullloses the reason for failure, making debugging harder.