ts-results
A typescript implementation of Rust's Result object.
README
ts-results
A typescript implementation of Rust's Result
and Option objects.
Brings compile-time error checking and optional values to typescript.
Installation
- ```bash
- $ npm install ts-results
- ```
or
- ```bash
- $ yarn add ts-results
- ```
Example
Result Example
Convert this:
- ```typescript
- import { existsSync, readFileSync } from 'fs';
- function readFile(path: string): string {
- if (existsSync(path)) {
- return readFileSync(path);
- } else {
- // Callers of readFile have no way of knowing the function can fail
- throw new Error('invalid path');
- }
- }
- // This line may fail unexpectedly without warnings from typescript
- const text = readFile('test.txt');
- ```
To this:
- ```typescript
- import { existsSync, readFileSync } from 'fs';
- import { Ok, Err, Result } from 'ts-results';
- function readFile(path: string): Result<string, 'invalid path'> {
- if (existsSync(path)) {
- return new Ok(readFileSync(path)); // new is optional here
- } else {
- return new Err('invalid path'); // new is optional here
- }
- }
- // Typescript now forces you to check whether you have a valid result at compile time.
- const result = readFile('test.txt');
- if (result.ok) {
- // text contains the file's content
- const text = result.val;
- } else {
- // err equals 'invalid path'
- const err = result.val;
- }
- ```
Option Example
Convert this:
- ```typescript
- declare function getLoggedInUsername(): string | undefined;
- declare function getImageURLForUsername(username: string): string | undefined;
- function getLoggedInImageURL(): string | undefined {
- const username = getLoggedInUsername();
- if (!username) {
- return undefined;
- }
- return getImageURLForUsername(username);
- }
- const stringUrl = getLoggedInImageURL();
- const optionalUrl = stringUrl ? new URL(stringUrl) : undefined;
- console.log(optionalUrl);
- ```
To this:
- ```typescript
- import { Option, Some, None } from 'ts-results';
- declare function getLoggedInUsername(): Option<string>;
- declare function getImageForUsername(username: string): Option<string>;
- function getLoggedInImage(): Option<string> {
- return getLoggedInUsername().andThen(getImageForUsername);
- }
- const optionalUrl = getLoggedInImage().map((url) => new URL(stringUrl));
- console.log(optionalUrl); // Some(URL('...'))
- // To extract the value, do this:
- if (optionalUrl.some) {
- const url: URL = optionalUrl.val;
- }
- ```
Usage
- ```typescript
- import { Result, Err, Ok } from 'ts-results';
- ```
Creation
- ```typescript
- let okResult: Result<number, Error> = Ok(10);
- let errorResult: Result<number, Error> = Err(new Error('bad number!'));
- ```
Type Safety
_Note: Typescript currently has a bug, making this type narrowing only work whenstrictNullChecks is turned on._
- ```typescript
- let result: Result<number, Error> = Ok(1);
- if (result.ok) {
- // Typescript knows that result.val is a number because result.ok was true
- let number = result.val + 1;
- } else {
- // Typescript knows that result.val is an `Error` because result.ok was false
- console.error(result.val.message);
- }
- if (result.err) {
- // Typescript knows that result.val is an `Error` because result.err was true
- console.error(result.val.message);
- } else {
- // Typescript knows that result.val is a number because result.err was false
- let number = result.val + 1;
- }
- ```
Stack Trace
A stack trace is generated when an Err is created.
- ```typescript
- let error = Err('Uh Oh');
- let stack = error.stack;
- ```
Unwrap
- ```typescript
- let goodResult = new Ok(1);
- let badResult = new Err(new Error('something went wrong'));
- goodResult.unwrap(); // 1
- badResult.unwrap(); // throws Error("something went wrong")
- ```
Expect
- ```typescript
- let goodResult = Ok(1);
- let badResult = Err(new Error('something went wrong'));
- goodResult.expect('goodResult should be a number'); // 1
- badResult.expect('badResult should be a number'); // throws Error("badResult should be a number - Error: something went wrong")
- ```
ExpectErr
- ```typescript
- let goodResult = Ok(1);
- let badResult = Err(new Error('something went wrong'));
- goodResult.expect('goodResult should not be a number'); // throws Error("goodResult should not be a number")
- badResult.expect('badResult should not be a number'); // new Error('something went wrong')
- ```
Map and MapErr
- ```typescript
- let goodResult = Ok(1);
- let badResult = Err(new Error('something went wrong'));
- goodResult.map((num) => num + 1).unwrap(); // 2
- badResult.map((num) => num + 1).unwrap(); // throws Error("something went wrong")
- goodResult
- .map((num) => num + 1)
- .mapErr((err) => new Error('mapped'))
- .unwrap(); // 2
- badResult
- .map((num) => num + 1)
- .mapErr((err) => new Error('mapped'))
- .unwrap(); // throws Error("mapped")
- ```
andThen
- ```typescript
- let goodResult = Ok(1);
- let badResult = Err(new Error('something went wrong'));
- goodResult.andThen((num) => new Ok(num + 1)).unwrap(); // 2
- badResult.andThen((num) => new Err(new Error('2nd error'))).unwrap(); // throws Error('something went wrong')
- goodResult.andThen((num) => new Err(new Error('2nd error'))).unwrap(); // throws Error('2nd error')
- goodResult
- .andThen((num) => new Ok(num + 1))
- .mapErr((err) => new Error('mapped'))
- .unwrap(); // 2
- badResult
- .andThen((num) => new Err(new Error('2nd error')))
- .mapErr((err) => new Error('mapped'))
- .unwrap(); // throws Error('mapped')
- goodResult
- .andThen((num) => new Err(new Error('2nd error')))
- .mapErr((err) => new Error('mapped'))
- .unwrap(); // thros Error('mapped')
- ```
Else
Deprecated in favor of unwrapOr
UnwrapOr
- ```typescript
- let goodResult = Ok(1);
- let badResult = Err(new Error('something went wrong'));
- goodResult.unwrapOr(5); // 1
- badResult.unwrapOr(5); // 5
- ```
Empty
- ```typescript
- function checkIsValid(isValid: boolean): Result<void, Error> {
- if (isValid) {
- return Ok.EMPTY;
- } else {
- return new Err(new Error('Not valid'));
- }
- }
- ```
Combining Results
ts-results has two helper functions for operating over n Result objects.
Result.all
Either returns all of the Ok values, or the first Err value
- ```typescript
- let pizzaResult: Result<Pizza, GetPizzaError> = getPizzaSomehow();
- let toppingsResult: Result<Toppings, GetToppingsError> = getToppingsSomehow();
- let result = Result.all(pizzaResult, toppingsResult); // Result<[Pizza, Toppings], GetPizzaError | GetToppingsError>
- let [pizza, toppings] = result.unwrap(); // pizza is a Pizza, toppings is a Toppings. Could throw GetPizzaError or GetToppingsError.
- ```
Result.any
Either returns the first Ok value, or all Err values
- ```typescript
- let url1: Result<string, Error1> = attempt1();
- let url2: Result<string, Error2> = attempt2();
- let url3: Result<string, Error3> = attempt3();
- let result = Result.any(url1, url2, url3); // Result
- let url = result.unwrap(); // At least one attempt gave us a successful url
- ```
Usage with rxjs
resultMap
Allows you to do the same actions as the normal rxjs map
operator on a stream of Result objects.
- ```typescript
- import { of, Observable } from 'rxjs';
- import { Ok, Err, Result } from 'ts-results';
- import { resultMap } from 'ts-results/rxjs-operators';
- const obs$: Observable<Result<number, Error>> = of(Ok(5), Err('uh oh'));
- const greaterThanZero = obs$.pipe(
- resultMap((number) => number > 0), // Doubles the value
- ); // Has type Observable
> - greaterThanZero.subscribe((result) => {
- if (result.ok) {
- console.log('Was greater than zero: ' + result.val);
- } else {
- console.log('Got Error Message: ' + result.val);
- }
- });
- // Logs the following:
- // Got number: 10
- // Got Error Message: uh oh
- ```
resultMapErr
- ```typescript
- import { resultMapErr } from 'ts-results/rxjs-operators';
- ```
Behaves exactly the same as resultMap, but maps the error value.
resultMapTo
- ```typescript
- import { resultMapTo } from 'ts-results/rxjs-operators';
- ```
Behaves the same as resultMap, but takes a value instead of a function.
resultMapErrTo
- ```typescript
- import { resultMapErrTo } from 'ts-results/rxjs-operators';
- ```
Behaves the same as resultMapErr, but takes a value instead of a function.
elseMap
Allows you to turn a stream of Result objects into a stream of values, transforming any errors into a value.
Similar to calling the else function, but works on a stream of Result objects.
- ```typescript
- import { of, Observable } from 'rxjs';
- import { Ok, Err, Result } from 'ts-results';
- import { elseMap } from 'ts-results/rxjs-operators';
- const obs$: Observable<Result<number, Error>> = of(Ok(5), Err(new Error('uh oh')));
- const doubled = obs$.pipe(
- elseMap((err) => {
- console.log('Got error: ' + err.message);
- return -1;
- }),
- ); // Has type Observable
- doubled.subscribe((number) => {
- console.log('Got number: ' + number);
- });
- // Logs the following:
- // Got number: 5
- // Got error: uh oh
- // Got number: -1
- ```
elseMapTo
- ```typescript
- import { elseMapTo } from 'ts-results/rxjs-operators';
- ```
Behaves the same as elseMap, but takes a value instead of a function.
resultSwitchMap and resultMergeMap
Allows you to do the same actions as the
normal rxjs switchMap
and rxjs switchMap operator on a stream of Result
objects.
- ```typescript
- import { of, Observable } from 'rxjs';
- import { Ok, Err, Result } from 'ts-results';
- import { resultMergeMap } from 'ts-results/rxjs-operators';
- const obs$: Observable<Result<number, Error>> = of(new Ok(5), new Err(new Error('uh oh')));
- const obs2$: Observable<Result<string, CustomError>> = of(new Ok('hi'), new Err(new CustomError('custom error')));
- const test$ = obs$.pipe(
- resultMergeMap((number) => {
- console.log('Got number: ' + number);
- return obs2$;
- }),
- ); // Has type Observable
> - test$.subscribe((result) => {
- if (result.ok) {
- console.log('Got string: ' + result.val);
- } else {
- console.log('Got error: ' + result.val.message);
- }
- });
- // Logs the following:
- // Got number: 5
- // Got string: hi
- // Got error: custom error
- // Got error: uh oh
- ```
filterResultOk
- ```typescript
- import { of, Observable } from 'rxjs';
- import { Ok, Err, Result } from 'ts-results';
- import { filterResultOk } from 'ts-results/rxjs-operators';
- const obs$: Observable<Result<number, Error>> = of(new Ok(5), new Err(new Error('uh oh')));
- const test$ = obs$.pipe(filterResultOk()); // Has type Observable
- test$.subscribe((result) => {
- console.log('Got number: ' + result);
- });
- // Logs the following:
- // Got number: 5
- ```
filterResultErr
- ```typescript
- import { of, Observable } from 'rxjs';
- import { Ok, Err, Result } from 'ts-results';
- import { filterResultOk } from 'ts-results/rxjs-operators';
- const obs$: Observable<Result<number, Error>> = of(new Ok(5), new Err(new Error('uh oh')));
- const test$ = obs$.pipe(filterResultOk()); // Has type Observable
- test$.subscribe((result) => {
- console.log('Got number: ' + result);
- });
- // Logs the following:
- // Got number: 5
- ```