| |

TypeScript 69 🔷 Result Types and Either Patterns

The previous chapter introduced the Result type as one option for typed error handling, alongside the try/catch and the custom error classes. This chapter takes the Result and its functional sibling Either further. They are the same idea with different names: a discriminated union that carries either a success value or a failure value, making the error part of the return type instead of a separate channel. The pattern is old — it comes from functional languages where exceptions are discouraged — and it is increasingly common in TypeScript because it forces the caller to handle both branches. The tradeoffs are real: the Result is more verbose than an exception, and it does not compose as naturally with the standard control flow. This chapter covers the Result‘s definition, the constructor functions, the combinators, the Either‘s alternative naming, the fp-ts library’s implementation, the patterns for chaining, the errors’ transformation, and the cases where the exceptions are the better tool.

Key point: A Result<T, E> is a discriminated union of { ok: true; value: T } and { ok: false; error: E }. The ok field is the discriminant, and the value and the error are the payloads. The construction is through the functions — the ok(value) and the err(error) — and the consumption is through the narrowing — the if (result.ok). The combinators — the map, the mapError, the flatMap, the unwrapOr, the match — transform and consume the result without the explicit narrowing. The Either<L, R> is the same shape with the Left and the Right instead of the ok and the err, and the fp-ts and the effect libraries provide the full combinator sets. The exceptions are the right tool for the truly exceptional; the Result is the right tool for the expected failures that the caller must handle.


The Result type

The Result is the discriminated union of the success and the failure. The ok field is the discriminant, and the two branches are the exclusive.

type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

The ok: true branch has the value: T, and the ok: false branch has the error: E. The two are the exclusive, and the discriminant is the ok.

Why the ok discriminant. The ok is the boolean, and the boolean is the discriminant. The if (result.ok) narrows the result to the success, and the else is the failure. The discriminant is the narrow’s key, and the key is the ok.

Why the two payloads are named. The value is the success’s, and the error is the failure’s. The two are the named, and the named is the clear. The alternative is the tuple, and the tuple is the less clear.

Why the Result is the union. The union is the discriminated, and the discriminated is the narrowable. The union is the two, and the two are the exclusive. The union is the model, and the model is the safety.

The constructors. The ok and the err functions construct the two branches.

function ok<T>(value: T): Result<T, never> {
  return { ok: true, value };
}

function err<E>(error: E): Result<never, E> {
  return { ok: false, error };
}

The ok(value) returns the Result<T, never>, and the err(error) returns the Result<never, E>. The never is the other branch’s absence, and the absence is the typing. The two functions are the constructors, and the constructors are the ergonomics.

Why the never in the constructors. The ok(value) has no error, so the error’s type is the never. The err(error) has no value, so the value’s type is the never. The never is the bottom, and the bottom is the absence. The two are the constructors, and the constructors are the precise.

Why the constructors are the functions. The ok and the err are the functions, and the functions are the reusable. The object literal is the direct, and the function is the concise. The two are the choice, and the choice is the style.

Why the Result is the return type. The function returns the Result<T, E>, and the two branches are the return. The return is the explicit, and the explicit is the safety. The caller must handle the two branches, and the handling is the requirement.

Why the Result is the alternative to the exception. The exception is the implicit, and the Result is the explicit. The exception is the control flow, and the Result is the value. The two are the choice, and the choice is the design.

Why the Result is the verbose. The Result requires the explicit narrowing, and the narrowing is the verbose. The exception is the automatic, and the automatic is the concise. The two are the tradeoff, and the tradeoff is the design. The Result is the explicit, and the explicit is the safety.


The combinators

The combinators transform and consume the Result without the explicit narrowing. The map, the mapError, the flatMap, the unwrapOr, and the match are the common.

The map. The map transforms the success’s value.

function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> {
  return result.ok ? ok(fn(result.value)) : result;
}

const doubled = map(ok(21), (n) => n * 2);
// doubled is Result<number, never> = { ok: true, value: 42 }

The map applies the fn to the success’s value, and the failure is passed through. The map is the transformation, and the transformation is the success’s.

Why the map matters. The map transforms the success without the explicit narrowing, and the transformation is the concise. The map is the combinator, and the combinator is the pattern.

The mapError. The mapError transforms the failure’s error.

function mapError<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F> {
  return result.ok ? result : err(fn(result.error));
}

const wrapped = mapError(err('failed'), (e) => new Error(e));
// wrapped is Result<never, Error> = { ok: false, error: Error('failed') }

The mapError applies the fn to the failure’s error, and the success is passed through. The mapError is the transformation, and the transformation is the failure’s.

Why the mapError matters. The mapError transforms the failure without the explicit narrowing, and the transformation is the concise. The mapError is the combinator, and the combinator is the pattern.

The flatMap. The flatMap chains the functions that return the Result.

function flatMap<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E> {
  return result.ok ? fn(result.value) : result;
}

const result = flatMap(ok(5), (n) => (n > 0 ? ok(n * 2) : err('negative')));
// result is Result<number, string> = { ok: true, value: 10 }

The flatMap applies the fn to the success’s value, and the fn returns the Result. The flatMap is the chain, and the chain is the pipeline. The flatMap is the then‘s equivalent, and the equivalent is the pattern.

Why the flatMap matters. The flatMap chains the functions, and the chain is the composition. The flatMap is the pipeline, and the pipeline is the pattern. The map returns the U, and the flatMap returns the Result<U, E>. The two are the difference, and the difference is the composition.

The unwrapOr. The unwrapOr returns the success’s value or the default.

function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {
  return result.ok ? result.value : defaultValue;
}

const value = unwrapOr(ok(42), 0);     // 42
const fallback = unwrapOr(err('x'), 0); // 0

The unwrapOr returns the value or the default, and the default is the fallback. The unwrapOr is the extraction, and the extraction is the default’s.

Why the unwrapOr matters. The unwrapOr extracts the value, and the default is the fallback. The unwrapOr is the safety, and the safety is the default. The unwrap (without the default) throws on the failure, and the unwrapOr does not. The two are the difference, and the difference is the safety.

The match. The match consumes both the branches with the two functions.

function match<T, E, U>(
  result: Result<T, E>,
  onOk: (value: T) => U,
  onErr: (error: E) => U,
): U {
  return result.ok ? onOk(result.value) : onErr(result.error);
}

const message = match(
  ok(42),
  (v) => `Value: ${v}`,
  (e) => `Error: ${e}`,
);
// message is 'Value: 42'

The match applies the onOk to the success and the onErr to the failure, and the result is the U. The match is the consumption, and the consumption is the both’s.

Why the match matters. The match consumes both the branches, and the two are the handled. The match is the alternative to the if, and the alternative is the expression. The match returns the U, and the U is the value. The match is the pattern, and the pattern is the functional.

Why the combinators matter. The combinators transform and consume the Result without the explicit narrowing, and the transformation is the concise. The map, the mapError, the flatMap, the unwrapOr, and the match are the five, and the five cover the cases. The combinators are the vocabulary, and the vocabulary is the fluency.


The Either‘s alternative naming

The Either<L, R> is the same shape as the Result, with the Left and the Right instead of the ok and the err. The Left is the failure, and the Right is the success.

type Either<L, R> = { _tag: 'Left'; left: L } | { _tag: 'Right'; right: R };

The _tag is the discriminant, and the Left and the Right are the two. The Left is the failure, and the Right is the success.

Why the Left is the failure. The Left is the convention, and the convention is the Latin’s (the sinister). The Right is the success, and the success is the right. The two are the convention, and the convention is the historical.

Why the _tag discriminant. The _tag is the discriminant, and the _tag is the fp-ts’s. The Result‘s ok is the boolean, and the Either‘s _tag is the string. The two are the difference, and the difference is the library’s.

Why the Either is the functional’s. The Either is the fp-ts’s, and the fp-ts is the functional’s. The Result is the object’s, and the object’s is the mainstream. The two are the same, and the naming is the difference.

Why the Either matters. The Either is the fp-ts’s, and the fp-ts is the ecosystem’s. The Either is the monad, and the monad is the composition. The Either is the functional, and the functional is the choice.

Why the Result and the Either are the equivalent. The two are the same shape, and the shape is the discriminated union. The naming is the difference, and the difference is the convention. The two are the interchangeable, and the interchangeable is the choice.


The fp-ts‘s implementation

The fp-ts library provides the Either with the full combinator set, and the pipe composes the functions.

import * as E from 'fp-ts/Either';
import { pipe } from 'fp-ts/function';

const result: E.Either<string, number> = pipe(
  E.right(5),
  E.map((n) => n * 2),
  E.chain((n) => (n > 0 ? E.right(n) : E.left('negative'))),
);

if (E.isRight(result)) {
  console.log(result.right);  // 10
} else {
  console.log(result.left);   // the error
}

The pipe composes the E.map and the E.chain, and the result is the Either<string, number>. The E.isRight narrows the result, and the result.right is the value.

Why the pipe matters. The pipe composes the functions, and the composition is the pipeline. The pipe is the fp-ts’s, and the pipe is the functional’s. The pipe is the composition, and the composition is the pattern.

Why the E.map and the E.chain matter. The E.map is the Result‘s map, and the E.chain is the Result‘s flatMap. The fp-ts‘s names are the functional’s, and the functional’s is the convention. The two are the same, and the naming is the difference.

Why the E.isRight and the E.isLeft. The E.isRight and the E.isLeft are the guards, and the guards narrow the Either. The E.isRight narrows to the Right, and the E.isLeft narrows to the Left. The two are the guards, and the guards are the safety.

Why the fp-ts is the choice. The fp-ts is the functional’s, and the functional’s is the choice. The fp-ts is the ecosystem’s, and the ecosystem’s is the value. The fp-ts is the library, and the library is the commitment. The two are the choice, and the choice is the team’s.


The chaining patterns

The Result‘s chaining is the pipeline, and the pipeline is the composition. The chaining patterns are the vocabulary.

The flatMap‘s chain. The flatMap chains the functions that return the Result.

const result = flatMap(
  fetchUser('1'),
  (user) =>
    flatMap(
      fetchOrders(user.id),
      (orders) => ok({ user, orders }),
    ),
);

The flatMap chains the fetchUser and the fetchOrders, and the result is the Result<{ user, orders }, ApiError>. The chain is the pipeline, and the pipeline is the composition.

Why the flatMap‘s chain matters. The flatMap‘s chain is the sequential, and the sequential is the dependency. The second’s fetchOrders depends on the first’s fetchUser, and the dependency is the chain. The flatMap is the chain, and the chain is the pattern.

The async‘s Result. The async function returns the Promise<Result<T, E>>, and the await is the extraction.

async function fetchUserSafe(id: string): Promise<Result<User, ApiError>> {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      return err(new ApiError(response.status, 'HTTP_ERROR', 'Request failed'));
    }
    const user = await response.json() as User;
    return ok(user);
  } catch (error) {
    return err(new ApiError(500, 'NETWORK_ERROR', 'Network failed'));
  }
}

The fetchUserSafe returns the Promise<Result<User, ApiError>>, and the two branches are the ok and the err. The async is the wrapper, and the Result is the return. The two are the combination, and the combination is the pattern.

Why the async’s Result matters. The async’s Result is the combination, and the combination is the pattern. The Promise is the async’s, and the Result is the sync’s. The two are the composition, and the composition is the Promise<Result<T, E>>.

The Promise.all‘s Result. The Promise.all combines the multiple results.

const results = await Promise.all([
  fetchUserSafe('1'),
  fetchUserSafe('2'),
]);

const users: User[] = [];
for (const result of results) {
  if (result.ok) users.push(result.value);
}

The results is the Result<User, ApiError>[], and the for iterates. The Promise.all is the parallel, and the Result is the each. The two are the combination, and the combination is the pattern.

Why the Promise.all‘s Result matters. The Promise.all‘s Result is the parallel, and the parallel is the batch. The Promise.all is the all-or-nothing, and the Result is the each. The two are the combination, and the combination is the pattern.

The Promise.allSettled‘s equivalent. The Promise.allSettled is the per-promise, and the Result is the equivalent.

const results = await Promise.all([...]);
// The results' equivalent is the Result[]

The Promise.allSettled is the per-promise, and the Result is the per-call. The two are the equivalent, and the equivalent is the pattern.

Why the equivalent matters. The Promise.allSettled is the standard, and the Result is the custom. The two are the same, and the same is the pattern. The Result is the explicit, and the explicit is the safety.

Why the chaining patterns matter. The chaining patterns are the vocabulary, and the vocabulary is the fluency. The flatMap‘s chain, the async’s Result, the Promise.all‘s Result, and the Promise.allSettled‘s equivalent are the four. The chaining is the composition, and the composition is the pattern.


When the Result is the right tool

The Result is the right tool for the expected failures that the caller must handle. The exceptions are the right tool for the truly exceptional.

The Result‘s cases. The validation’s failures, the parsing’s failures, the network’s expected errors, the business’s rules’ violations. The caller must handle the failures, and the failures are the expected.

The exceptions’ cases. The programming’s errors, the invariant’s violations, the environment’s failures, the truly exceptional. The caller does not handle the failures, and the failures are the unexpected.

Why the distinction matters. The distinction is the design, and the design is the value. The Result is the explicit, and the explicit is the safety. The exception is the implicit, and the implicit is the concise. The two are the tradeoff, and the tradeoff is the design.

Why the Result can be the verbose. The Result requires the explicit narrowing, and the narrowing is the verbose. The Result‘s chain is the nested, and the nested is the complex. The two are the cost, and the cost is the tradeoff.

Why the exceptions can be the concise. The exception is the automatic, and the automatic is the concise. The exception is the unwinding, and the unwinding is the control flow. The two are the benefit, and the benefit is the tradeoff.

Why the combination can be the best. The combination uses the Result for the expected, and the exception for the unexpected. The two are the combination, and the combination is the design. The Result is the return, and the exception is the throw. The two are the same, and the same is the choice.

Why the team’s choice matters. The team’s choice is the consistency, and the consistency is the value. The team’s convention is the source, and the source is the team’s. The Result or the exception is the team’s, and the team’s is the correct.


Complete Example Session

// ============================================
// PART 1: THE RESULT TYPE
// ============================================

type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

// ============================================
// PART 2: THE CONSTRUCTORS
// ============================================

function ok<T>(value: T): Result<T, never> {
  return { ok: true, value };
}

function err<E>(error: E): Result<never, E> {
  return { ok: false, error };
}

// ============================================
// PART 3: THE MAP
// ============================================

function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> {
  return result.ok ? ok(fn(result.value)) : result;
}

const doubled = map(ok(21), (n) => n * 2);
// doubled is Result<number, never> = { ok: true, value: 42 }

// ============================================
// PART 4: THE MAPERROR
// ============================================

function mapError<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F> {
  return result.ok ? result : err(fn(result.error));
}

const wrapped = mapError(err('failed'), (e) => new Error(e));
// wrapped is Result<never, Error> = { ok: false, error: Error('failed') }

// ============================================
// PART 5: THE FLATMAP
// ============================================

function flatMap<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E> {
  return result.ok ? fn(result.value) : result;
}

const result = flatMap(ok(5), (n) => (n > 0 ? ok(n * 2) : err('negative')));
// result is Result<number, string> = { ok: true, value: 10 }

// ============================================
// PART 6: THE UNWRAPOR AND THE MATCH
// ============================================

function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {
  return result.ok ? result.value : defaultValue;
}

function match<T, E, U>(
  result: Result<T, E>,
  onOk: (value: T) => U,
  onErr: (error: E) => U,
): U {
  return result.ok ? onOk(result.value) : onErr(result.error);
}

const value = unwrapOr(ok(42), 0);     // 42
const fallback = unwrapOr(err('x'), 0); // 0
const message = match(ok(42), (v) => `Value: ${v}`, (e) => `Error: ${e}`);

// ============================================
// PART 7: THE ASYNC RESULT
// ============================================

class ApiError extends Error {
  constructor(readonly status: number, readonly code: string, message: string) {
    super(message);
    this.name = 'ApiError';
  }
}

async function fetchUserSafe(id: string): Promise<Result<User, ApiError>> {
  try {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      return err(new ApiError(response.status, 'HTTP_ERROR', 'Request failed'));
    }
    const user = await response.json() as User;
    return ok(user);
  } catch (error) {
    return err(new ApiError(500, 'NETWORK_ERROR', 'Network failed'));
  }
}

// ============================================
// PART 8: THE FLATMAP'S CHAIN
// ============================================

const chain = flatMap(
  await fetchUserSafe('1'),
  (user) =>
    flatMap(
      await fetchOrdersSafe(user.id),
      (orders) => ok({ user, orders }),
    ),
);
// The chain is the pipeline.

// ============================================
// PART 9: THE FP-TS'S EITHER
// ============================================

import * as E from 'fp-ts/Either';
import { pipe } from 'fp-ts/function';

const fpResult: E.Either<string, number> = pipe(
  E.right(5),
  E.map((n) => n * 2),
  E.chain((n) => (n > 0 ? E.right(n) : E.left('negative'))),
);

if (E.isRight(fpResult)) {
  console.log(fpResult.right);  // 10
}

// ============================================
// PART 10: WHAT NOT TO DO
// ============================================

// Don't use the Result for the programming's errors
// The truly exceptional should be the exception.

// Don't forget the narrowing
// result.value  // ❌ without the if (result.ok)

// Don't mix the Result and the exception without the discipline
// The two are the choice.

// Don't use the unwrap without the reason
// The unwrap throws on the failure.

// Don't forget the async's Result
// The Promise<Result<T, E>> is the combination.

// Don't use the Result for the exceptional
// The Result is for the expected.

The ten parts cover the Result type, the constructors, the map, the mapError, the flatMap, the unwrapOr and the match, the async’s Result, the flatMap‘s chain, the fp-ts‘s Either, and the anti-patterns.


Quick Reference

The Result Type

The branchThe shape
The success{ ok: true; value: T }
The failure{ ok: false; error: E }
The discriminantThe ok

The Constructors

The functionThe return
ok(value)The Result<T, never>
err(error)The Result<never, E>

The Combinators

The combinatorThe purpose
mapThe success’s transform
mapErrorThe failure’s transform
flatMapThe chain
unwrapOrThe extraction with the default
matchThe both’s consumption

The Either‘s Naming

The ResultThe Either
okThe Right
errThe Left
The valueThe right
The errorThe left
The ok‘s discriminantThe _tag

The fp-ts‘s Functions

The functionThe purpose
E.rightThe success
E.leftThe failure
E.mapThe success’s transform
E.chainThe chain
E.isRightThe guard
E.isLeftThe guard
pipeThe composition

The When-to-Use

The caseThe tool
The expected failureThe Result
The validation’s failureThe Result
The programming’s errorThe exception
The invariant’s violationThe exception
The environment’s failureThe exception

Best Practices

✅ Do This:

// Use the Result for the expected
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }; // ✅

// Use the constructors
return ok(user);                                               // ✅

// Use the combinators
const doubled = map(ok(21), (n) => n * 2);                     // ✅

// Use the flatMap for the chain
const result = flatMap(fetchUser('1'), (u) => fetchOrders(u.id)); // ✅

// Use the match for the consumption
const message = match(result, (v) => `OK: ${v}`, (e) => `Err: ${e}`); // ✅

// Use the async's Result
async function fetchSafe(): Promise<Result<User, ApiError>> { ... } // ✅

// Use the fp-ts's Either for the functional
pipe(E.right(5), E.map((n) => n * 2));                         // ✅

❌ Don’t Do This:

// Don't use the Result for the programming's errors
function divide(a: number, b: number): Result<number, string> {
  if (b === 0) throw new Error('Division by zero');  // the exception
  return ok(a / b);
}                                                              // ⚠️

// Don't forget the narrowing
result.value  // ❌ without the if (result.ok)                  // ⚠️

// Don't mix the Result and the exception without the discipline
// The two are the choice.                                     // ⚠️

// Don't use the unwrap without the reason
const value = unwrap(err('x'));  // the throws                    // ⚠️

// Don't forget the async's Result
async function fetch(): Promise<User> { ... }  // the exception's  // ⚠️

// Don't use the Result for the exceptional
// The Result is for the expected.                             // ⚠️

Common Pitfalls

PitfallProblemSolution
The Result for the exceptionalThe verboseUse the exception
The missing narrowingThe wrong accessThe if (result.ok)
The unwrap‘s throwThe hidden failureThe unwrapOr
The mixed modelsThe inconsistentThe team’s choice
The forgotten async’s ResultThe exception’sThe Promise<Result<T, E>>
The flatMap‘s chain’s nestingThe complexThe pipe
The Either‘s naming’s confusionThe Left and the RightThe convention
The combinators’ missingThe verboseThe combinators

Real-World Examples

1. The Result type

type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

2. The constructors

const success = ok(42);
const failure = err(new ApiError(404, 'NOT_FOUND', 'User not found'));

3. The map

const doubled = map(ok(21), (n) => n * 2);

4. The mapError

const wrapped = mapError(err('failed'), (e) => new Error(e));

5. The flatMap

const result = flatMap(ok(5), (n) => (n > 0 ? ok(n * 2) : err('negative')));

6. The match

const message = match(result, (v) => `OK: ${v}`, (e) => `Err: ${e}`);

7. The async’s Result

async function fetchSafe(id: string): Promise<Result<User, ApiError>> { ... }

8. The flatMap‘s chain

const chain = flatMap(fetchUser('1'), (u) => fetchOrders(u.id));

9. The fp-ts‘s Either

pipe(E.right(5), E.map((n) => n * 2), E.chain((n) => n > 0 ? E.right(n) : E.left('neg')));

10. The guard

if (E.isRight(fpResult)) console.log(fpResult.right);

Visual: The Result

┌──────────────────────────────────────────────────────────┐
│  type Result<T, E> =                                     │
│    | { ok: true; value: T }                              │
│    | { ok: false; error: E };                            │
│                                                          │
│  THE SUCCESS                                             │
│    { ok: true, value: 42 }                               │
│                                                          │
│  THE FAILURE                                             │
│    { ok: false, error: 'failed' }                        │
│                                                          │
│  THE NARROWING                                           │
│    if (result.ok) {                                      │
│      result.value  // the access                         │
│    } else {                                              │
│      result.error  // the access                         │
│    }                                                     │
│                                                          │
│  The two branches are the exclusive.                     │
│                                                          │
└──────────────────────────────────────────────────────────┘

Visual: The Combinators

┌──────────────────────────────────────────────────────────┐
│  map(result, fn)                                         │
│    The success → the fn → the new Result                 │
│    The failure → the pass-through                        │
│                                                          │
│  mapError(result, fn)                                    │
│    The failure → the fn → the new Result                 │
│    The success → the pass-through                        │
│                                                          │
│  flatMap(result, fn)                                     │
│    The success → the fn → the Result                     │
│    The failure → the pass-through                        │
│    The chain                                             │
│                                                          │
│  unwrapOr(result, default)                               │
│    The success → the value                               │
│    The failure → the default                             │
│                                                          │
│  match(result, onOk, onErr)                              │
│    The success → the onOk → the U                        │
│    The failure → the onErr → the U                       │
│                                                          │
└──────────────────────────────────────────────────────────┘

Visual: The flatMap’s Chain

┌──────────────────────────────────────────────────────────┐
│  flatMap(fetchUser('1'), (user) =>                       │
│    flatMap(fetchOrders(user.id), (orders) =>             │
│      ok({ user, orders })                                │
│    )                                                     │
│  )                                                       │
│                                                          │
│  THE FLOW                                                │
│                                                          │
│  fetchUser('1')                                          │
│    │                                                     │
│    ├── The success → the user                            │
│    │     │                                               │
│    │     ▼                                               │
│    │   fetchOrders(user.id)                              │
│    │     │                                               │
│    │     ├── The success → the orders                    │
│    │     │     └── ok({ user, orders })                  │
│    │     │                                               │
│    │     └── The failure → the pass-through              │
│    │                                                     │
│    └── The failure → the pass-through                    │
│                                                          │
│  The chain is the pipeline.                              │
│                                                          │
└──────────────────────────────────────────────────────────┘

Visual: The Result and the Exception

┌──────────────────────────────────────────────────────────┐
│  THE RESULT                                              │
│    The explicit, the return type                         │
│    The caller must handle                                │
│    The verbose, the safe                                 │
│    The expected failures                                 │
│                                                          │
├──────────────────────────────────────────────────────────┤
│  THE EXCEPTION                                           │
│    The implicit, the throw                               │
│    The caller may ignore                                 │
│    The concise, the risky                                │
│    The unexpected failures                               │
│                                                          │
│  THE COMBINATION                                         │
│    The Result for the expected                           │
│    The exception for the unexpected                      │
│    The team's choice                                     │
│                                                          │
└──────────────────────────────────────────────────────────┘

Visual: The fp-ts’s Either

┌──────────────────────────────────────────────────────────┐
│  import * as E from 'fp-ts/Either';                      │
│  import { pipe } from 'fp-ts/function';                  │
│                                                          │
│  const result = pipe(                                    │
│    E.right(5),                                           │
│    E.map((n) => n * 2),                                  │
│    E.chain((n) => (n > 0 ? E.right(n) : E.left('negative'))),│
│  );                                                      │
│                                                          │
│  THE GUARD                                               │
│    if (E.isRight(result)) {                              │
│      console.log(result.right);  // 10                   │
│    } else {                                              │
│      console.log(result.left);   // the error            │
│    }                                                     │
│                                                          │
│  The Either is the functional's Result.                  │
│                                                          │
└──────────────────────────────────────────────────────────┘

Summary

ItemValue
The ResultThe { ok: true; value } | { ok: false; error }
The constructorsThe ok(value) and the err(error)
The mapThe success’s transform
The mapErrorThe failure’s transform
The flatMapThe chain
The unwrapOrThe extraction with the default
The matchThe both’s consumption
The EitherThe { _tag: 'Left' } | { _tag: 'Right' }
The fp-tsThe E.right, the E.left, the E.map, the E.chain
The when-to-useThe expected vs the exceptional

Key takeaways:

  • The Result<T, E> is the discriminated union of the success and the failure — the ok is the discriminant, and the value and the error are the payloads
  • The constructors are the ok(value) and the err(error) — the never in the other branch’s position is the typing
  • The combinators transform and consume the Result without the explicit narrowing — the map, the mapError, the flatMap, the unwrapOr, and the match
  • The flatMap is the chain — the functions return the Result, and the chain is the pipeline
  • The Either<L, R> is the same shape with the Left and the Right — the Left is the failure, and the Right is the success, and the fp-ts provides the full set
  • The fp-ts‘s pipe composes the functions — the E.map and the E.chain are the functional’s names for the map and the flatMap
  • The Result is the right tool for the expected failures — the validation, the parsing, the network’s expected errors, the business’s rules
  • The exceptions are the right tool for the truly exceptional — the programming’s errors, the invariant’s violations, the environment’s failures
  • The async’s Result is the Promise<Result<T, E>> — the combination is the pattern, and the Promise.all‘s Result is the parallel’s
  • The team’s choice is the consistency — the Result or the exception, but not the mix without the discipline

Remember: The Result and the Either are the typed error handling’s tools. The Result is the discriminated union, the ok is the discriminant, and the combinators are the transformation. The flatMap is the chain, the match is the consumption, and the unwrapOr is the default. The Either is the functional’s, and the fp-ts is the library. The Result is for the expected, the exception is for the unexpected, and the team’s choice is the consistency. The typed errors are the safety, and the safety is the value.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!