npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

result-async

v1.3.0

Published

Error handling

Downloads

10

Readme

Result-Async

A library for handling errors with types and without exceptions - even asynchronous ones.

npm package CircleCI David Dependency Status codecov

result-async focuses on making a few things easier in JavaScript/TypeScript:

  1. Predictable error handling for synchronous and asynchronous functions
  2. Handling errors in pipelines of synchronous and asynchronous functions
  3. Making pipelines easy to read
  4. Giving friendly names to proven functional programming concepts

Install

npm i result-async pipeout --save
# or
yarn add result-async pipeout

Then import the functions and (in TypeScript) types you need:

import { ok, okThen, okChainAsync, Result } from "result-async";
import { pipeA } from "pipeout";

Useful addition: a pipe functions

result-async is designed to work with a pipe function. Ramda has pipe and pipeP functions, and Lodash has flow. But pipe and flow don't handle promise-returning functions, and pipeP won't handle adding a non-promise function to the middle of your pipeline.

For a typesafe pipe that handles async functions, you can use this author's pipeout. pipeout.pipeA works like a normal pipe, but every function you add to the pipeline is partially applied, and you use .value to get the final result. That turns

c(await b(a(await x)));

into

pipe(x)(a)(b)(c).value;

It has a few other varients of pipe, you can see the pipeout docs for more information.

Install pipeout

If you want to use pipeout, you just have to install and import it.

npm i result-async pipeout --save
# or
yarn add result-async pipeout
import { pipeA } from "pipeout";

Examples

Here are some example of what working with result-async looks like. For more examples, and complete documentation, see the docs.

Full Example

result-async helps you handle errors in a controlled, readable, and typesafe way, using techniques from functional programming.

Here's an example of the sort of data-processing pipelines you can build:

async function countAllComments(postsCache: PostsCache): Promise<number> {
  // prettier-ignore
  return pipeA
    (promiseToResult(postsCache.getCache()))
    (errorDo(console.error))
    (errorRescueAsync(fetchPosts))
    (okDo(logPostCount))
    (okDoAsync(postsCache.updateCache))
    (okThen(R.map(fetchCommentsForPost)))
    (okChainAsync(allOkAsync))
    (okThen(R.map(comments => comments.length)))
    (okThen(R.sum))
    (okDo(logCommentTotal))
    (okOrThrow)
    .value;
}

The basic premise is functions that start with ok will only operate on successful data, and pass on error messages. Functions that start with error will only operate on error messages, and either change or try to rescue them back into successes.

Prefixing the function names makes it easy to scan a pipeline and find the error handling code.

Other functions transform other data structures to Results (like promiseToResult), or handle collections of Results (like allOkAsync).

Functions ending in async will return a Promise that resolves to a Result. This helps TypeScript track types, and helps developers differentiate between pure and impure functions.

See ./src/full-example for a full working example.

To run the example, run:

yarn ts-node -O '{"module": "commonjs"}' src/full-example.ts

Techniques

React to errors inline

import { isError } from "result-async";

const result = await tryToGetAListOfComments("my-blog-post");

const comments = isOk(result) ? result.ok : [];

Return a success or failure status

import { ok, error } from "result-async";

return isAllWell() ? ok("all is well") : error("all is lost");

Kick off asynchronous calls without waiting for a response

import { fetchUser, errorDo, errorRescueAsync } from "result-async";
import { pipeA } from "pipeout";

// prettier-ignore
pipeA
  (fetchUser())
  (errorDo(sendErrorToLoggingSystem))
  (errorRescueAsync(logOutUser))
);

Transform promise functions into result functions

import { resultify, errorThen, okChainAsync } from "result-async";
import { pipeA } from "pipeout";

const fetchResult = resultify(fetch);

// prettier-ignore
pipeA
  (someUrl)
  (fetchResult)
  (errorThen(transformError))
  (okChainAsync(anotherAsyncFunction))

Return standard promises to interop with other functions

import { okChain, errorReplace } from "result-async";
import { pipeA } from "pipeout";

function doStuff(): Promise<SomeData> {
  // prettier-ignore
  return pipeA(
    (someResultFunction)
    (okChain(validateData))
    (errorReplace("something went wrong"))
    (okOrThrow)
}

Typescript

result-async is written in TypeScript, and it's built to help write typesafe error handling code. But it works great with vanilla JavaScript too!

One of the big benefits of ResultP, a Promise of a Result, is that Promises don't have type information about their error case. But many promises have predictable error types. So, you can have your asynchronous functions return a ResultP to declare those errors types in the type system.

Also ResultPs should always resolve and never be rejected. This means no more try/catch blocks, even when using async/await.

Types

The important types are:

type Result<OkData, ErrorMessage> = OkResult<Data> | ErrorResult<Message>;
type ResultP<OkData, ErrorMessage> = Promise<Result<OkData, ErrorMessage>>;

So a Result could be either Ok or an Error - and either way, the payload is strongly typed.

Guards

isOk(result) and isError(result) are both typeguards, so if isOk returns true, typescript will know the Result is actually an OkResult:

function(result: Result<number, string>) {
  // Type error: ok could be undefined.
  result.ok + 1

  if (isOk(result)) {
    // No type error, ok is definitely defined.
    result.ok + 1
  }
}

Testing with Result-Async

If you're using a library like Jest for testing, you can generally follow your testing library's advice for testing asynchronous code.

Unlike with standard promises, you don't have to worry about errored ResultPs throwing errors.

You'll probably want to test if your calls succeed or fail. If you want to check both the result and payload, try matching against another result:

import { ok } from "result-async";

it("should be fine", async () => {
  expect(await myResultyFunction()).toEqual(ok("all is well"));
});

If you only want to check if a call succeeded for failed, you can just check for the presence of "ok" or "error".

it("should be fine", async () => {
  expect(await myResultyFunction()).toHaveProperty("ok");
});

For more complicated checks you can use okOrThrow/Error to throw an error if the result isn't what you expect, and otherwise extract the payload for further testing. For example:

expect(okOrThrow(await myResultyFunction())).toContain("is well");

Background

Async Error Handling

async/await has been a huge win for avoiding the dreaded Pyramid of doom in JavaScript.

These bad days are behind us forever:

someFunction(data, (err, data) => {
  if (err) throw err;
  secondFunction(data, (err, data) => {
    if (err) throw err;
    thirdFunction(data, (err, data) => {
      if (err) throw err;

      doStuff(data);
    });
  });
});

When we can now use async/await to do this:

try {
  data = await someFunction(data);
  data = await secondFunction(data);
  data = await thirdFunction(data);
} catch (e) {
  doSomethingWithError(e);
}

Much better. But still, errors are treated really differently than normal data. And in TypeScript, errors are also untyped. For a genuine exception that's fine, but there are a lot of cases where I expect a call to sometimes fail (for instance, a request to an endpoint that could 404). In those cases, having to rely on catching errors can feel a little heavyweight.

The functional programming world has an answer for this - the Result type. It's in Haskell, Ocaml, Elixir, and more. The idea is to have a function that could fail return a response that's marked as either a Success or a Failure. Then you can react to that result status, pass it along, ignore it, or whatever - just like any other data structure.

In many FP languages, we have pattern matching to make this easier:

case some_async_function() do
  {:ok, data} -> do_something(data)
  {:error, msg} -> do_something_else(msg)
end

We don't have that in JavaScript, but result-async tries to make it easy to create and handle Results - even when they come from async functions:

import { either } from "result-async";

either(
  await someAsyncFunction(),
  data => doSomething(data),
  msg => doSomethingElse(msg)
);

What about Fantasy-Land

If you come from a Haskell-y background, you might be saying, "hey, Result is just an Either type, and okThen is just map". You're right! And if you're looking for more abstract functional programming, you may be interested in libraries like Sanctuary or Folktalk, which provide a Fantasy-Land compatible Either and Result types, respectively. Fluture is also a great tool for Fantasy-Land compatible asynchronous programming.

But if your team isn't ready that all that, think of Result-Async like a gateway drug for full ADT-style programming. It lets you write composable, functional programs, but with functions names that are trying to be friendlier to people who don't think in monands.

Publishing Updates

yarn docs
npm version patch | minor | major
npm publish