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

@opliko/eventual-result

v0.12.1

Published

A `Result` and `Option` implementation that works with `Promise`

Downloads

81

Readme

eventual-result

A Result and Option implementation that works with Promise

Installation

This package is authored using Deno and can be found on deno.land:

import { type Result } from "https://deno.land/x/eventual_result/mod.ts";

For consumption through npm (or other Node package managers), a compatible package is generated through dnt:

npm install eventual-result

Usage

For the full documentation of this package, check out the hosted documentation on doc.deno.land.

Option

An Option represents a value that can be present (Some), but might not be (None):

type Person = {
  id: number;
  name: string;
};

function findPerson(id: number): Option<Person> {
  if (store.people.has({ id })) {
    return new Some(store.people.get({ id }));
  } else {
    return None;
  }
}

Result

A Result represents a value that could be represent either a successful result (Ok) or an error that occurred instead (Err):

type Person = {
  name: string;
  age: number;
};

function createAdult(name: string, age: number): Result<Person, string> {
  if (age < 18) {
    return new Err(`${age} is too young to be an adult`);
  } else {
    return new Ok({ name, age });
  }
}

EventualResult

An EventualResult is a cross between a Promise and a Result. Unlike a Promise it will never reject, and the same methods that can be called on a Result can be called on an EventualResult. Like a Promise, an EventualResult can be resolved (using .then or await) to retrieve the inner Result.

Why Another Result Implementation?

There are many other libraries that implement these patterns; a few sources of inspiration include ts-results and oxide.ts. These libraries share a problem, however: they do nothing to help you manage asynchronous code.

Since the dawn of ES6, Promise and async/await have become critical parts of working on a modern JavaScript or TypeScript application. While Result provides a great way to model a potential success or failure for a synchronous action, needing to use something like Promise<Result> to model an asynchronous action can get you into trouble:

Let's suppose that we want to read a file asynchronously and then validate it to produce a Result. That might look something like this:

import { readFile } from "node:fs/promises";

declare function isValid(content: string): boolean;

function validateFile(content: string): Result<string, string> {
  if (isValid(content)) {
    return Ok(content);
  } else {
    return Err("The file content is not valid");
  }
}

async function readValidFile(path: string): Promise<Result<string, string>> {
  const content = await readFile(path);

  return validateFile(content);
}

// Let's say that `path/to/file.txt` points to a location that does not exist
const potentiallyValidFile = await readValidFile("path/to/file.txt");

What happens here? An exception will be thrown! Even though we want to be using Result to model an error state, readFile doesn't know anything about that; the awaited promise rejects and an exception is thrown.

This example shows how using Promise and Result together can cause errors, because our Result does nothing to help us with the rejected Promise. This isn't impossible to solve though: we can use try/catch to ensure a reject Promise results in an Err:

Let's improve on our last example by ensuring that an error from readFile doesn't cause readValidFile to result in a rejected Promise!

import { readFile } from "node:fs/promises";

declare function isValid(content: string): boolean;

function validateFile(content: string): Result<string, string> {
  if (isValid(content)) {
    return new Ok(content);
  } else {
    return new Err("The file content is not valid");
  }
}

async function readValidFile(path: string): Promise<Result<string, string>> {
  try {
    const content = await readFile(path);

    return validateFile(content);
  } catch (e: unknown) {
    return new Err(String(e));
  }
}

// Let's say that `path/to/file.txt` points to a location that does not exist
const potentiallyValidFile = await readValidFile("path/to/file.txt");

What happens this time? Rather than throwing an exception, we get a resolution to an Err. Success!

But... can we do better? What are some problems with the code above?

  • Having to defensively wrap every asynchronous function in a try/catch doesn't feel good. While we do want to be exhaustive about handling errors, we don't want to have to write defensive code. Additionally, when you are working in a codebase that has adopted the Result pattern, these locations where try/catch are required to wrap third-party code really stand out.
  • We lose the top-to-bottom readability of the readValidFile function. The error handling for readFile is way down at the bottom instead of being anywhere near the actual function call.

What might a solution to these problems look like?

Enter EventualResult! This class provides us an alternative way to represent an eventual value that may result in a success or failure that, rather than competing with Result, is compatible with it.

Let's look at the same example, but this time making use of an EventualResult instead of a Promise<Result>:

import { readFile } from "node:fs/promises";

declare function isValid(content: string): boolean;

function validateFile(content: string): Result<string, string> {
  if (isValid(content)) {
    return new Ok(content);
  } else {
    return new Err("The file content is not valid");
  }
}

function readValidFile(path: string): EventualResult<string> {
  return new EventualResult(readFile(path)).andThen((content) =>
    validateFile(content)
  );
}

// Let's say that `path/to/file.txt` points to a location that does not exist
const eventualPotentiallyValidFile = readValidFile("path/to/file.txt");

What has changed?

  1. We no longer need specific try/catch wrapping around readFile; by passing it through EventualResult, we no longer end up with a Promise that can reject. If an error during the file read occurs, the EventualResult will resolve to an Err.
  2. We don't need any conditional logic when validating the file that handles what to do when the file read failed. EventualResult implements most of the same methods that Result does, we can lean on our existing knowledge about working with Result to only validate the contents if the file read eventually results in an Ok.

The end result is writing less defensive code that extends the predictability of Result with the eventual resolution of Promise.