Skip to content

Use await while respecting failure cases without exceptions

License

Notifications You must be signed in to change notification settings

bendyworks/rah-rah

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RahRah

Use Javascript await while respecting failure cases without exceptions.

Why does this exist?

If you want to use await and still get data from failure cases, you must use try/catch:

// old way

async function myFunc() {
  let result;
  try {
    result = await someAsyncFunc();
    doSomething(result);
  } catch (e) {
    handleError(e);
  }
}

// new way with rah-rah

import { R } from 'rah-rah';

async function myFunc() {
  const result = await R(someAsyncFunc());

  if (result.good) {
    doSomething(result.ok);
  } else {
    handleError(result.err);
  }
}

// or even

import { R } from 'rah-rah';

async function myFunc(): string {
  const result = await R(someAsyncFunc());

  return (
    result
      .map(doSomething)
      .mapErr(handleError)
      .withDefault('n/a')
  );
}

If you recognize this kind of thing from the Functional Programming world, it's basically Result/Either mapped inside a Promise... or something like that.

Usage

A simple example app is available in the examples folder.

Creation

Insert a call to R between await and your promise:

import { R } from 'rah-rah';

const result = await R(aPromise);
const otherResult = await R(ajaxAsPromise(url));

Did it fail or succeed?

If you use withDefault, map, and mapErr (shown below), you shouldn't need these.

import { R } from 'rah-rah';

async function go(aPromise: Promise<...>) {
  const result = await R(aPromise);
  if (result.good) {
    // ...
  } else { ... }

  // or

  if (result.bad) {
    // ...
  }
}

Raw results

Use result.ok and result.err. If you use withDefault, map, and mapErr (shown below), you shouldn't need these.

import { R } from 'rah-rah';

async function go(aPromise: Promise<...>) {
  const result = await R(aPromise);
  if (result.good) {
    doSomething(result.ok);
  } else { ... }

  // or

  if (result.bad) {
    handleError(result.err);
  }
}

Collapsing errors

Perhaps you don't care about an error? Or you've correctly handled the error, and you'd like to use either a successful value or a default value in the case of a failure:

import { R } from 'rah-rah';

async function go(aPromise: Promise<string>): Promise<string> {
  const result = await R(aPromise);
  return result.withDefault('An error occurred.');
}

If you want the default to use the underlying error value, use applyDefault:

import { R } from 'rah-rah';

async function go(aPromise: Promise<string>): Promise<string> {
  const result = await R(aPromise);
  return result.applyDefault((err: Error) => `An error of type '${err.name}' occurred.`);
}

Changing ("map"ing) successful values

Need to change ("map") the successful value? Use map:

import { R } from 'rah-rah';

async function go(aPromise: Promise<number>): Promise<RahRah<Error, string>> {
  const result = await R(aPromise);
  return result.map(ok => `answer: ${ok}`); // still wrapped in a RahRah object!
}

Changing ("map"ing) failure values

Need to change ("map") the failure value? Use mapErr:

import { R } from 'rah-rah';

async function go(aPromise: Promise<number>): Promise<RahRah<string, number>> {
  const result = await R(aPromise);
  return result.mapErr(err => err.message.toLowerCase()); // still wrapped in a RahRah object!
}

Collapsing errors & results

Perhaps you need to do something with both successful and failure situations, and return an unwrapped value:

import { R } from 'rah-rah';

function double(result: RahRah<Error, number>): string {
  return result.flatten(ok => {
    return `doubled: ${ok * 2}`;
  }, err => {
    informExceptionHandler(err);
    return err.message.toLowerCase();
  });
}

const result = await R(aPromise);
console.log(double(result));

Installation

This library requires TypeScript, with at least es2015 compatibility.

$ npm install typescript --save-dev
$ npm install rah-rah --save

Other similar libraries

await-to-js

await-to-js provides a wonderfully simple API that does nearly the same thing:

import to from 'await-to-js';

const [err, user] = await to(UserModel.findById(1));

The problem is that await-to-js assumes that you'll never want to use null as a real error value; that is, you can never have a null value for err. Similarly, you're disallowed from returning (as unlikely as it is) undefined as your successful value.

These edge cases are nuanced and very unlikely, but they exist. RahRah avoids these edge cases.

License

License terms can be found in the LICENSE file.

Author

RahRah was written by: