Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/joanllenas/ts.data.either

A Typescript implementation of the Either data type
https://github.com/joanllenas/ts.data.either

either left result right typescript

Last synced: 15 days ago
JSON representation

A Typescript implementation of the Either data type

Awesome Lists containing this project

README

        

# Either

[![Build Status](https://travis-ci.org/joanllenas/ts.data.either.svg?branch=master)](https://travis-ci.org/joanllenas/ts.data.either)
[![npm version](https://badge.fury.io/js/ts.data.either.svg)](https://badge.fury.io/js/ts.data.either)

The `Either` data type encapsulates the idea of a computation that may fail.

An `Either` value can either be `Right` some value or `Left` some error.

```ts
type Either = Right | Left;
```

> If this is new to you, you may want to read the introductory article [Safer code with container types](https://blog.logrocket.com/safer-code-with-container-types-either-and-maybe/) about why and how to use this library.

## Install

```
npm install ts.data.either --save
```

## Example

```ts
import { Either, tryCatch, andThen, withDefault } from './either';

interface UserJson {
id: number;
nickname: string;
email: string;
}

// throws if file does not exists
const readFile = (filename: string): string => {
const fileSystem: { [key: string]: string } = {
'something.json': `
[
{
"id": 1,
"nickname": "rick",
"email": "[email protected]"
},
{
"id": 2,
"nickname": "morty",
"email": "[email protected]"
}
]`
};
const fileContents = fileSystem[filename];
if (fileContents === undefined) {
throw new Error(`${filename} does not exists.`);
}
return fileContents;
};

// Wraps the read file operation in an Either
const readFileContent = (filename: string): Either =>
tryCatch(
() => readFile(filename),
err => err
);

// Wraps the json parsing operation in an Either
const parseJson = (json: string): Either =>
tryCatch(
() => JSON.parse(json),
err => new Error(`There was an error parsing this Json.`)
);

// The pipeline function just makes function invocations flow
const pipeline = (initialValue: any, ...fns: Function[]) =>
fns.reduce((acc, fn) => fn(acc), initialValue);

const usersFull: UserJson[] = pipeline(
'something.json',
(fname: string) => readFileContent(fname),
(json: Either) => andThen(parseJson, json),
(users: Either) => withDefault(users, [])
); // returns the Array of users because all intermediate operations have succeeded

const usersEmpty: UserJson[] = pipeline(
'nothing.json',
(fname: string) => readFileContent(fname),
(json: Either) => andThen(parseJson, json),
(users: Either) => withDefault(users, [])
); // returns an empty Array because the readFile operations failed
```

## Api

_(Inspired by elm-lang)_

### right

`right(value: T): Either`

Wraps a value in an instance of `Right`.

```ts
right(5); // Right(5)
```

### left

`left(error: Error): Either`

Creates an instance of `Left`.

```ts
left(new Error('Something bad happened')); // Left(Error('Something bad happened'))
left(new Error('The calculation failed')); // Left(Error('The calculation failed'))
```

### isRight

`isRight(value: Either): boolean`

Returns true if a value is an instance of `Right`.

```ts
isRight(left(new Error('Wrong!'))); // false
```

### isLeft

`isLeft(value: Either): boolean`

Returns true if a value is not an instance of `Right`.

```ts
isLeft(right(5)); // false
isLeft(left('Hi!')); // true
isLeft(null); // true
```

### withDefault

`withDefault(value: Either, defaultValue: T): T`

If `value` is an instance of `Right` it returns its wrapped value, if it's an instance of `Left` it returns the `defaultValue`.

```ts
withDefault(right(5), 0); // 5
withDefault(left(new Error('Wrong!')), 0); // 0
```

### caseOf

`caseOf(caseof: {Right: (v: A) => B; Left: (err: Error) => B;}, value: Either): B`

Run different computations depending on whether an `Either` is `Right` or `Left` and returns the result.

```ts
caseOf(
{
Left: err => `Error: ${err.message}`,
Right: n => `Launch ${n} missiles`
},
right('5')
); // 'Launch 5 missiles'
```

### map

`map(f: (a: A) => B, value: Either): Either`

Transforms an `Either` value with a given function.

```ts
const add1 = (n: number) => n + 1;
map(add1, right(4)); // Right(5)
map(add1, left(new Error('Something bad happened'))); // Left('Something bad happened')
```

### tryCatch

`tryCatch(f: () => A, onError: (e: Error) => Error): Either`

Transforms a function (that might throw an exception) that produces `A` to a function that produces `Either`.

```ts
tryCatch(
() => JSON.parse(''),
err => err
); // Left('Unexpected end of JSON input')
```

### andThen

`andThen(f: (a: A) => Either, value: Either): Either`

Chains together computations that may fail.

```ts
const removeFirstElement = (arr: T[]): T[] => {
if (arr.length === 0) {
throw new Error('Array is empty');
}
return arr.slice(1);
};

const safeRemoveFirst = (arr: T[]): Either => {
try {
return right(removeFirstElement(arr));
} catch (error) {
return left(error);
}
};

// The pipeline function just makes function invocations flow
const pipeline = (initialValue: any, ...fns: Function[]) =>
fns.reduce((acc, fn) => fn(acc), initialValue);

const result: string[] = pipeline(
['a', 'b', 'c'],
safeRemoveFirst, // Right(['b', 'c'])
(arr: Either) => andThen(safeRemoveFirst, arr), // Right(['b'])
(arr: Either) => andThen(safeRemoveFirst, arr), // Right([])
(arr: Either) => andThen(safeRemoveFirst, arr), // Left(Error('Array is empty'))
(arr: Either) => andThen(safeRemoveFirst, arr), // Left(Error('Array is empty'))
(arr: Either) => withDefault(arr, [])
);

console.log(result); // []
```