Result

Result represents the result of an operation that can either be successful (Ok) or a failure (Error). It's commonly used to represent the result of a function that may fail, such as a network request, a file read, or a database query.

Constructors

ok

Constructs an Ok Result with the provided value.

import { Result } from 'funkcia';

//      β”Œβ”€β”€β”€ Result<number, never>
//      β–Ό
const result = Result.ok(10);

of

Alias of Result.ok

Constructs an Ok Result with the provided value.

import { Result } from 'funkcia';

//      β”Œβ”€β”€β”€ Result<number, never>
//      β–Ό
const result = Result.of(10);

error

Constructs an Error result with the provided value.

import { Result } from 'funkcia';

function divide(dividend: number, divisor: number): Result<number, InvalidDivisor> {
  if (divisor === 0) {
    return Result.error(new InvalidDivisor());
  }

  return Result.ok(dividend / divisor);
}

fromNullable

Constructs a Result from a nullable value.

If the value is null or undefined, it returns a Result.Error with a NoValueError error, or with the value returned by the provided onNullable callback. Otherwise, it returns a Result.Ok.

import { Result } from 'funkcia';

declare const user: User | null;

//      β”Œβ”€β”€β”€ Result<User, NoValueError>
//      β–Ό
const result = Result.fromNullable(user);

//            β”Œβ”€β”€β”€ Result<string, UserNotFound>
//            β–Ό
const resultWithCustomError = Result.fromNullable(
  user,
  () => new UserNotFound(),
);

fromFalsy

Constructs a Result from a falsy value.

If the value is falsy, it returns a Result.Error result with a NoValueError error, or with the value returned by the provided onFalsy callback. Otherwise, it returns a Result.Ok.

import { Result } from 'funkcia';

interface User {
  id: string;
  firstName: string;
  lastName: string | null;
}

//      β”Œβ”€β”€β”€ Result<string, NoValueError>
//      β–Ό
const result = Result.fromFalsy(user.lastName?.trim());

//            β”Œβ”€β”€β”€ Result<string, Error>
//            β–Ό
const resultWithCustomError = Result.fromFalsy(
  user.lastName?.trim(),
  () => new Error('User missing last name'),
);

try

Constructs a Result from a function that may throw.

If the function executes successfully, it returns a Result.Ok. Otherwise, it returns a Result.Error containing an UnknownError with the thrown exception, or with the value returned by the provided onThrow callback.

import { Result } from 'funkcia';

//     β”Œβ”€β”€β”€ Result<URL, UnknownError>
//     β–Ό
const url = Result.try(() => new URL('example.com'));
// Output: Error(UnknownError)

//          β”Œβ”€β”€β”€ Result<URL, Error>
//          β–Ό
const urlWithCustomError = Result.try(
  () => new URL('example.com'),
  (error) => new Error('Invalid URL'),
);
// Output: Error('Invalid URL')

predicate

Returns a function that asserts that a value passes the test implemented by the provided function, creating a Result.Ok, with the value tested, if the predicate is fulfilled.

If the test fails, returns a Result.Error with a FailedPredicateError instead, or with the value returned by the provided onUnfulfilled callback.

import { Result } from 'funkcia';

// With type guard
//          β”Œβ”€β”€β”€ (shape: Shape) => Result
//          β–Ό
const ensureCircle = Result.predicate(
  (shape: Shape): shape is Circle => shape.kind === 'circle',
  () => new InvalidShapeError(shape.kind),
);

// With regular predicate
//          β”Œβ”€β”€β”€ (value: number) => Result
//          β–Ό
const ensurePositive = Result.predicate(
  (value: number) => value > 0,
  (value) => new InvalidNumberError(value),
);

fun

Declare a function that always returns a Result.

import { Result } from 'funkcia';

// When defining a normal function allowing typescript to infer the return type,
// the return type is always a union of `Result<T, never>` and `Result<never, E>`
function hasAcceptedTermsOfService(user: User) {
  if (typeof user.termsOfService !== 'boolean') {
    return Result.error('NOT ACCEPTED' as const);
  }

  return user.termsOfService ?
      Result.ok('ACCEPTED' as const)
    : Result.error('REJECTED' as cons);
}

//       β”Œβ”€β”€β”€ Result<'ACCEPTED', never> | Result<never, 'REJECTED'> | Result<never, 'NOT ACCEPTED'>
//       β–Ό
const result = hasAcceptedTermsOfService(user);

// When using the `fun` method, the return type is always `Result<T, E>`
const improvedHasAcceptedTermsOfService = Result.fun(hasAcceptedTermsOfService);

//       β”Œβ”€β”€β”€ Result<'ACCEPTED', 'REJECTED' | 'NOT ACCEPTED'>
//       β–Ό
const result = improvedHasAcceptedTermsOfService(user);

enhance

Converts a function that may throw an exception to a function that returns a Result.

import { Result } from 'funkcia';

//         β”Œβ”€β”€β”€ (text: string, reviver?: Function) => Result<any, TypeError>
//         β–Ό
const safeJsonParse = Result.enhance(
  JSON.parse,
  (error) => new TypeError('Invalid JSON'),
);

//       β”Œβ”€β”€β”€ Result<any, TypeError>
//       β–Ό
const result = safeJsonParse('{ "name": "John Doe" }');
// Output: Ok({ name: 'John Doe' })

values

Given an array of Results, returns an array containing only the values inside Result.Ok.

import { Result } from 'funkcia';

//       β”Œβ”€β”€β”€ number[]
//       β–Ό
const output = Result.values([
  Result.ok(1),
  Result.error<number>('Failed computation'),
  Result.ok(3),
]);
// Output: [1, 3]

is

Asserts that an unknown value is a Result.

import { Result } from 'funkcia';

declare const maybeAResultWithUser: unknown;

if (Result.is(maybeAResultWithUser)) {
//                     β”Œβ”€β”€β”€ Result<unknown, unknown>
//                     β–Ό
  const user = maybeAResultWithUser.filter(isUser);
//        β–²
//        └─── Result<User, FailedPredicateError<unknown>>
}

Instance methods

map

Applies a callback function to the value of the Result when it is Ok, returning a new Result containing the new value.

import { Result } from 'funkcia';

//       β”Œβ”€β”€β”€ Result<number, never>
//       β–Ό
const result = Result.ok(10).map(number => number * 2);
// Output: Ok(20)

mapError

Applies a callback function to the value of the Result when it is Error, returning a new Result containing the new error value.

import { Result } from 'funkcia';

declare const user: User | null;

//       β”Œβ”€β”€β”€ Result<User, UserNotFound>
//       β–Ό
const result = Result.fromNullable(user).mapError(
  (error) => new UserNotFound()
//   β–²
//   └─── NoValueError
);

mapBoth

Maps both the Result value and the Result error to new values.

import { Result } from 'funkcia';

//       β”Œβ”€β”€β”€ Result<string, UserMissingInformationError>
//       β–Ό
const result = Result.fromNullable(user.lastName).mapBoth({
  Ok: (lastName) => `Hello, Mr. ${lastName}`,
  Error: (error) => new UserMissingInformationError(),
//          β–²
//          └─── NoValueError
});

andThen

Applies a callback function to the value of the Result when it is Ok, and returns the new value. Similar to chain (also known as flatMap).

import { Result } from 'funkcia';

declare function readFile(path: string): Result<string, FileNotFoundError | FileReadError>;
declare function parseJsonFile(contents: string): Result<FileContent, InvalidJsonError>;

//       β”Œβ”€β”€β”€ Result<FileContent, FileNotFoundError | FileReadError | InvalidJsonError>
//       β–Ό
const result = readFile('data.json').andThen(parseJsonFile);

filter

Asserts that the Result value passes the test implemented by the provided function. Can narrow types and customize error handling.

import { Result } from 'funkcia';

declare const input: Shape;

//       β”Œβ”€β”€β”€ Result<Circle, FailedPredicateError<Square>>
//       β–Ό
const result = Result.of(input).filter(
  (shape): shape is Circle => shape.kind === 'CIRCLE',
);

//            β”Œβ”€β”€β”€ Result<Circle, Error>
//            β–Ό
const resultWithCustomError = Result.of(input).filter(
  (shape): shape is Circle => shape.kind === 'CIRCLE',
  (shape) => new Error(`Expected Circle, received ${shape.kind}`),
//   β–²
//   └─── Square
);

or

Replaces the current Result with the provided fallback Result when it is Error.

import { Result } from 'funkcia';

const personalEmail = Result.ok('[email protected]')
  .or(() => Result.ok('[email protected]'))
  .unwrap();
// Output: '[email protected]'

const workEmail = Result.error(new Error('Missing personal email'))
  .or(() => Result.ok('[email protected]'))
  .unwrap();
// Output: '[email protected]'

swap

Swaps the Result value and error. If Ok, returns Error with the value. If Error, returns Ok with the error.

import { Result } from 'funkcia';

declare function getCachedUser(email: Email): Result<User, CacheMissError<Email>>;
declare function findOrCreateUserByEmail(email: Email): User;

//       β”Œβ”€β”€β”€ Result<User, User>
//       β–Ό
const result = getCachedUser('[email protected]')
  .swap() // Result<CacheMissError<Email>, User>
  .map((cacheMiss) => findOrCreateUserByEmail(cacheMiss.input));
//         β–²
//         └─── CacheMissError<Email>

zip

Combines two Results into a single Result containing a tuple of their values, if both Results are Ok variants, otherwise, returns Result.Error.

import { Result } from 'funkcia';

const first = Result.some('hello');
const second = Result.some('world');

//       β”Œβ”€β”€β”€ Result<[string, string], never>
//       β–Ό
const strings = first.zip(second);
// Output: Ok(['hello', 'world'])

zipWith

Combines two Results into a single Result. The new value is produced by applying the given function to both values, if both Results are Ok variants, otherwise, returns Error.

import { Result } from 'funkcia';

const first = Result.some('hello');
const second = Result.some('world');

//        β”Œβ”€β”€β”€ Result<string, never>
//        β–Ό
const greeting = first.zipWith(second, (a, b) => `${a} ${b}`);
// Output: Ok('hello world')

match

Compare the Result against the possible patterns and then execute code based on which pattern matches.

import { Result } from 'funkcia';

declare function readFile(path: string): Result<string, FileNotFoundError | FileReadError>;
declare function parseSalesRecords(content: string): Result<SalesRecord[], InvalidSalesRecordFileError>;
declare function aggregateSales(salesRecords: SalesRecord[]): AggregatedSaleRecord[];

//     β”Œβ”€β”€β”€ AggregatedSaleRecord[]
//     β–Ό
const data = readFile('data.json')
  .andThen(parseSalesRecords)
  .match({
    Ok(contents) {
      return aggregateSales(contents);
    },
//          β”Œβ”€β”€β”€ FileNotFoundError | FileReadError | InvalidSalesRecordFileError
//          β–Ό
    Error(error) {
      return []
    },
  });

unwrap

Unwraps the Result value.

import { Result } from 'funkcia';

//      β”Œβ”€β”€β”€ number
//      β–Ό
const number = Result.ok(10).unwrap();

Result.error(new Error('Β―\_(ツ)_/Β―')).unwrap();
// Output: Uncaught exception: 'called "Result.unwrap()" on an "Error" value'

unwrapError

Unwraps the Result error.

import { Result } from 'funkcia';

const result = Result.error(new UserNotFound());

if (result.isError()) {
  const error = result.unwrapError();
//        β–²
//        └─── UserNotFound
}

unwrapOr

Returns the value of the Result. If the Result is Error, returns the fallback value.

import { Result } from 'funkcia';

//       β”Œβ”€β”€β”€ string
//       β–Ό
const baseUrl = Result.ok('https://funkcia.lukemorales.io')
  .unwrapOr(() => 'http://localhost:3000');
// Output: 'https://funkcia.lukemorales.io'

const apiKey = Result.error('Missing API key')
  .unwrapOr(() => 'sk_test_9FK7CiUnKaU');
// Output: 'sk_test_9FK7CiUnKaU'

unwrapOrNull

Use this method at the edges of the system, when storing values in a database or serializing to JSON.

Unwraps the value of the Result if it is a Ok, otherwise returns null.

import { Result } from 'funkcia';

declare const findUserById: (id: string) => Result<User, UserNotFound>;

//     β”Œβ”€β”€β”€ User | null
//     β–Ό
const user = findUserById('user_123').unwrapOrNull();

unwrapOrUndefined

Use this method at the edges of the system, when storing values in a database or serializing to JSON.

Unwraps the value of the Result if it is a Ok, otherwise returns undefined.

import { Result } from 'funkcia';

declare const findUserById: (id: string) => Result<User, UserNotFound>;

//     β”Œβ”€β”€β”€ User | undefined
//     β–Ό
const user = findUserById('user_123').unwrapOrUndefined();

expect

Unwraps the Result value.

import { Result } from 'funkcia';

declare function findUserById(id: string): Result<User, NoValueError>;

//     β”Œβ”€β”€β”€ User
//     β–Ό
const user = findUserById(userId).expect(
  (error) => new UserNotFound(userId)
//   β–²
//   └─── NoValueError
);

merge

Returns the value inside the Result. If Ok, returns the value; if Error, returns the error.

import { Result } from 'funkcia';

declare function getCachedUser(email: Email): Result<User, CacheMissError<Email>>;
declare function getOrCreateUserByEmail(email: Email): User;

//       β”Œβ”€β”€β”€ User
//       β–Ό
const result = getCachedUser('[email protected]')
  .swap() // Result<CacheMissError<Email>, User>
  .map((cacheMiss) => getOrCreateUserByEmail(cacheMiss.input)) // Result<User, User>
  .merge();
// Output: { id: 'user_123', email: '[email protected]' }

contains

Returns true if the predicate is fulfilled by the wrapped value. If the predicate is not fulfilled or the Result is Error, it returns false.

import { Result } from 'funkcia';

//         β”Œβ”€β”€β”€ boolean
//         β–Ό
const isPositive = Result.ok(10).contains(num => num > 0);
// Output: true

const isNegative = Result.error(10).contains(num => num < 0);
// Output: false

toArray

Converts a Result to an array. If Result is Ok, returns an array with the value. If Result is Error, returns an empty array.

import { Result } from 'funkcia';

//       β”Œβ”€β”€β”€ number[]
//       β–Ό
const output = Result.ok(10).toArray();
// Output: [10]

toOption

Converts a Result to an Option. If Result is Ok, returns an Option.Some. If Result is Error, returns an Option.None.

import { Result } from 'funkcia';

declare function readFile(path: string): Result<string, FileNotFoundError | FileReadError>;
declare function parseSalesRecords(content: string): Result<SalesRecord[], InvalidSalesRecordFileError>;

//          β”Œβ”€β”€β”€ Option<SalesRecord[]>
//          β–Ό
const fileContents = readFile('data.json')
  .andThen(parseSalesRecords)
  .toOption();

toAsyncOption

Converts the Result to an AsyncOption.

import { Result } from 'funkcia';

declare function readFile(path: string): Result<string, FileNotFoundError | FileReadError>;
declare function parseSalesRecords(content: string): Result<SalesRecord[], InvalidSalesRecordFileError>;

//       β”Œβ”€β”€β”€ AsyncOption<SalesRecord[]>
//       β–Ό
const asyncFile = readFile('data.json')
  .andThen(parseSalesRecords)
  .toAsyncOption();
// Output: Promise<Some(SalesRecord[])>

toAsyncResult

Converts the Result to an AsyncResult.

import { Result } from 'funkcia';

declare function readFile(path: string): Result<string, FileNotFoundError | FileReadError>;
declare function parseSalesRecords(content: string): Result<SalesRecord[], InvalidSalesRecordFileError>;

//       β”Œβ”€β”€β”€ AsyncResult<SalesRecord[], FileNotFoundError | FileReadError | InvalidSalesRecordFileError>
//       β–Ό
const asyncFile = readFile('data.json')
  .andThen(parseSalesRecords)
  .toAsyncResult();
// Output: Promise<Ok(SalesRecord[])>

tap

Calls the function with the Result value, then returns the Result itself, ignoring the returned value of the provided function.

This allows "tapping into" a function sequence in a pipe, to perform side effects on intermediate results.

import { Result } from 'funkcia';

//       β”Œβ”€β”€β”€ Result<number, never>
//       β–Ό
const result = Result.some(10).tap(
  (value) => console.log(value), // Console output: 10
);

tapError

Calls the function with the Result error, then returns the Result itself, ignoring the returned value of the provided function.

This allows "tapping into" a function sequence in a pipe, to perform side effects on intermediate results.

import { Result } from 'funkcia';

declare function findUserById(id: string): Result<User, UserNotFound>;

//       β”Œβ”€β”€β”€ Result<User, UserNotFound>
//       β–Ό
const result = findUserById('invalid_id').tapError(
  (error) => console.log(error), // Console output: UserNotFound
);

isOk

Returns true if the Result contains a value.

import { Option } from 'funkcia';

declare function findUserById(id: string): Result<User, UserNotFound>;

const user = findUserById('user_123');

if (user.isOk()) {
  return user.unwrap(); // `unwrap` will not throw
}

isError

Returns true if the Result contains an error.

import { Option } from 'funkcia';

declare function findUserById(id: string): Result<User, UserNotFound>;

const user = findUserById('invalid_id');

if (user.isError()) {
  const error = user.unwrapError(); // `unwrapError` will not throw
  // ...process error
}

return user.unwrap();

equals

Compares the Result with another Result and returns true if they are equal.

import { Result } from 'funkcia';

const result = Result.of(10).equals(Result.ok(10));
// Output: true

Last updated

Was this helpful?