0.1.65 • Published 13 days ago

okrs v0.1.65

Weekly downloads
-
License
MIT
Repository
-
Last release
13 days ago

okrs

Background

This is a "result class" library. It's for handling runtime errors gracefully without try-catch statementscommunicating .

Here's the pain:

Do you ever find yourself wondering where exactly you should throw an error to be consumed by a try-catch? Do you sometimes end up having multiple layers of try-catch blocks? Should you just return null instead? — Khalil Stemmler (https://khalilstemmler.com/articles/enterprise-typescript-nodejs/handling-errors-result-class/)

See the whole article from Khalil for his version of a result class that can address the problem.

Also notice that programming languages like Elm guarantee no runtime exceptions with a similar pattern (https://guide.elm-lang.org/error_handling/).

Quickstart

One of these:

pnpm add okrs // yarn add okrs // npm install okrs

Then

import { okrs } from 'okrs'

Instead of try/catch

const $kr = okrs.coerce(() => {
  return mightFailFn() as string
})

if (!$kr.success) {
  // Then handle error
  return
}
const value = $kr.value  // This will be type "string" if we return in the if block

Handle specific failures

function fragileFn(): okrs.Either<string, 'error-code-1' | 'error-code-2'> {
  if (process.env.FOO === 1) {
    return okrs.fail('error-code-1')
  }
  if (process.env.FOO === 2) {
    return okrs.fail('error-code-2')
  }
  return okrs.ok('success')
}

function main() {
  const $kr = fragileFn()
  if (!$kr.success) {
    switch ($kr.code) {
      case 'error-code-1':
        return 'fallback-1';
      case 'error-code-2':
        return 'fallback-2';
      default:
        throw $kr
    }
  }
  return $kr.value
}

Handle rejection(s) in list of promises

How do you handle errors in Promise.all? You can't really. Read about the headache here: https://stackoverflow.com/questions/30362733/handling-errors-in-promise-all

But with this utility it's easy:

const $kr = okrs.map([1, 2], async (num) => {
  await sleep(num)
  if (num % 2 === 1) {
    throw new Error('1 is no good')
  }
  return num
}) : Either<number[]>

It knows to wait until all the promises are resolved AND handles multiple failures.

Types

type Either<R, C> = Ok<R> | <Fail<C>

Your functions should return this type or Promise<Either<T, C>>. Then the implementation logic can discriminate using .success:

  • If .success is false, then you have a Fail<C> object
  • If .success is true, then you have an Ok<T> object

type Ok<T>

type Ok<R> {
  success: true;
  code: null;
  value: R;
}

type Fail<C>

type Fail<C> {
  success: false;
  code: C;
  value: null;
  status: number
  extra: any // See [extra](#extra)
}

Methods

okrs.ok(R): Ok<R>

This is how you return an Ok object.

okrs.fail(C, Extra): Fail<C>

Call this to return a Fail object.

okrs.map()

This is the recommended way to map through a list and create a series of promises to run in parallel. This returns a single Either or Promise<Either>. The Ok value will be the list of results if all promises resolve. If any promise rejects than you'll get back a Fail with the first error code/message from the series.

const $kr = okrs.map([1, 2], async (num) => {
  await sleep(num)
  if (num % 2 === 1) {
    throw new Error('1 is no good')
  }
  return num
}) : Either<number[]>

okrs.all()

This

const kr = await okrs.all([
  ok(1),
  ok(false),
  Promise.resolve(ok(1)),
  Promise.resolve(ok(false)),
]): Promise<{
  "success": true,
  "value": [
    1,
    false,
    1,
    false,
  ],
}>

okrs.props()

  • Like .all but for object properties instead of iterated values.
  • It can take async or syncrounous functions and return a Promise appropriately
const kr = await okrs.props({
  a: ok(1),
  b: ok(false),
  c: sleep(1).then(() => ok(1)),
  d: sleep(2).then(() => ok(false)),
}): Promise<{
  "success": true,
  "value": {
    "a": 1,
    "b": false,
    "c": 1,
    "d": false,
  }
}>

okrs.strict()

This will immediately invoke the function argument and throw if a Fail object is returned. It will also turn any uncaught errors into Fail objects, before throwing those.

const b = strict(
  () => {
    if (!process.env.SOME_ENV) throw new Error('foobar');
    return 1;
  },
  {
    foo: 'bar',
  }
)

This will throw the following error:

Fail {
  success: false
  code: 'foobar',
  extra; {
    foo: 'bar'
  }
}
0.1.65

13 days ago

0.1.63

3 months ago

0.1.64

3 months ago

0.1.62

3 months ago

0.1.58

3 months ago

0.1.59

3 months ago

0.1.60

3 months ago

0.1.61

3 months ago

0.1.56

5 months ago

0.1.57

5 months ago

0.1.52

7 months ago

0.1.53

6 months ago

0.1.54

6 months ago

0.1.55

6 months ago

0.1.50

7 months ago

0.1.51

7 months ago

0.1.49

7 months ago

0.1.48

7 months ago

0.1.41

8 months ago

0.1.42

8 months ago

0.1.43

8 months ago

0.1.44

8 months ago

0.1.45

8 months ago

0.1.46

8 months ago

0.1.47

8 months ago

0.1.40

8 months ago

0.1.38

8 months ago

0.1.39

8 months ago

0.1.36

8 months ago

0.1.37

8 months ago

0.1.30

9 months ago

0.1.31

9 months ago

0.1.32

8 months ago

0.1.10

10 months ago

0.1.33

8 months ago

0.1.11

10 months ago

0.1.34

8 months ago

0.1.12

10 months ago

0.1.35

8 months ago

0.1.13

10 months ago

0.1.14

10 months ago

0.1.15

10 months ago

0.1.27

10 months ago

0.1.28

9 months ago

0.1.29

9 months ago

0.1.20

10 months ago

0.1.21

10 months ago

0.1.22

10 months ago

0.1.23

10 months ago

0.1.24

10 months ago

0.1.25

10 months ago

0.1.26

10 months ago

0.1.2

11 months ago

0.1.16

10 months ago

0.1.8

10 months ago

0.1.17

10 months ago

0.1.7

10 months ago

0.1.18

10 months ago

0.1.19

10 months ago

0.1.9

10 months ago

0.1.4

11 months ago

0.1.3

11 months ago

0.1.6

10 months ago

0.1.5

11 months ago