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

might-fail

v0.3.1

Published

Return an Either object instead of throwing an exception

Downloads

111

Readme

Might Fail

A TypeScript library for handling async and sync errors without try and catch blocks. Inspired by other languages that utilize Result or Either types for safer error handling. The following examples are verbose to show how you would handle different types of errors differently instead of just catching all errors together and handling them in the same way. However, you can use mightFail to handle all errors in the same way if you want.

Async

Wrap Promise in mightFail

const { error, result } = await mightFail(axios.get("/posts"));

if (error) {
  // handle error
  return;
}

const posts = result.data
posts.map((post) => console.log(post.title));

or:

const { error: networkError, result } = await mightFail(fetch("/posts"));

if (networkError) {
  // handle network error
  return;
}

if (!result.ok) {
  // handle an error response from server
  return;
}

const { error: convertToJSONError, result: posts } = await mightFail(
  result.json()
);

if (convertToJSONError) {
  // handle convertToJSONError
  return;
}

posts.map((post) => console.log(post.title));

Or Wrap Async Function in makeMightFail

const get = makeMightFail(axios.get);
const { error, result } = await get("/posts");

if (error) {
  // handle error
  return;
}

const posts = result.data
posts.map((post) => console.log(post.title));

Sync

Wrap throwing functions in mightFailSync

const {error, result} = mightFailSync(() => JSON.parse("")); // JSON.parse might throw
if (error) {
  console.error('Parsing failed:', error);
  return
}
console.log('Parsed object:', result);

Or Wrap Sync Function in makeMightFailSync

function parseJSON(jsonString: string) {
  return JSON.parse(jsonString); // This might throw
}
const safeParseJSON = makeMightFailSync(parseJSON);

const { error, result } = safeParseJSON("");

if (error) {
  console.error("Parsing failed:", error);
  return;
}
console.log("Parsed object:", result);

Either Type

awaiting the mightFail functions will return an Either type with either an error or a result.

  • error always has the type Error | undefined.
  • result always has the type T | undefined where T is the type of the result of the promise passed to mightFail.

This means that the you never lose the type information of the result of the promise passed to mightFail.


Motivation

I think throwing exceptions is cool, I like that an exception breaks control flow and I like exception propogation. The only thing I don't like catching exceptions. This mostly happens at the most "user facing" part of the code like an api endpoint or a UI component, the outer most function call. So catching an exception needs to notify the user that something went wrong, log the error for debugging, and stop the currently execution flow.

Guard ✅

Guarding allows you to handle your errors early and return from the function early, making them more readable and easier to reason about.

const { error: networkError, result } = await mightFail(fetch("/posts"));
// guard against a network error
if (networkError) {
  return;
}
// guard against an error response from the server
if (!result.ok) {
  return;
}
const { error: convertToJSONError, result: posts } = await mightFail(
  result.json()
);
// guard against an error converting the response to JSON
if (convertToJSONError) {
  return;
}

// success case, unnested and at the bottom of the function
posts.map((post) => console.log(post.title));

The success case is now the only code that is not nested in an if statement. It's also at the very bottom of the function making it easy to find.

Everything in One Try/Catch Block ❌

try {
  const response = await fetch("/posts");

  if (!response.ok) {
    // handle an error response from server
    return;
  }
  const posts = await response.json();

  posts.map((post) => console.log(post.title));
} catch (error) {
  // handle any errors, not sure which one though 🤷‍♀️
}

This is bad because:

  • Error handling happens in multiple places in the function.
  • The catch block will catch any and all errors which makes it difficult to handle different errors differently.
  • All the success case code will happen inside of the try block

Multiple Try/Catch Blocks ❌

let response: Response;
try {
  response = await fetch("/posts");
} catch (error) {
  // guard against a network error
  return;
}
if (!response.ok) {
  // guard against an error response from server
  return;
}

let posts: Post[];
try {
  posts = await response.json();
} catch (error) {
  // guard against an error converting the response to JSON
  return;
}

posts.map((post) => console.log(post.title));

Declaring the variable ahead of time is a little weird and it makes infering the type of the variable a little more difficult. Also, try catch finally blocks can be confusing.

try catch finally can be confusing ❌

function something() {
  try {
    throw new Error("something went wrong");
  } catch(error) {
    console.log("error happened")
    return "error return"
  } finally {
    console.log("finally happened")
    return "finally return"
  }
  return "something return"
}
console.log(something())

Can every single dev in your team understand what the above code will print out?