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

@eznix/try

v2.0.7

Published

Handle errors as values with ease

Downloads

9

Readme

Try

Bundle size

Concept

This package aims to simulate an approach where errors are treated as values, similar to programming languages like Rust and Golang, where errors are explicit.

Check also Railway Oriented Programming.

In Rust, using the Result Pattern:

fn main() {
    let result = File::open("hello.txt");

    let greeting_file = match result {
        Ok(file) => file,
        Err(error) => // handle errors,
    };
}

In Golang, errors are implicit:

f, err := os.Open("filename.ext")
if err != nil {
  // handle errors
}

Why?

Errors are not explicit in Javascript. Importing an external library or using your own code can introduce errors that can be difficult to track down. So your code is based on trust alone.

import { fetchUser } from "./user";
import { leftpad } from "leftpad";

await fetchUser(); // should we handle the errors ?
leftpad(); // should we handle the errors ?

Try is a wrapper for your functions. It enforces the need to handle errors and errors is not thrown, they are returned as values. The library also aims to eliminate callback hell (nested then) and the tower of doom (try-catch block).

By treating errors as values, the project simplifies error handling, making it more explicit and reducing the chances of overlooking errors.

[!NOTE] We also support synchronous function too. Use trySync, it will return no promise.

Getting Started

bun add @eznix/try
yarn install @eznix/try
pnpm add @eznix/try
npm install @eznix/try

Wrapping Operations

import { trySync, tryAsync } from "@eznix/try";

// `fetchUser` is an async function
const tryFetchUser = await tryAsync(fetchUser);
// `fetchUser` is an sync function
const tryFetchUser = trySync(fetchUser);

Handling Results

Access Successful Value

const user = await tryFetchUser.getOrElse({"id": 1, "name": "jimmy"}); // Use a default value if the operation failed

Inspect Error

const error = await tryFetchUser.error(); // Get the error if the operation failed

Recover from Failure

// Compared to `getOrElse()`, you can try other actions, like another fetch.
// It will return errors as a value.
const recoveredTry = await tryFetchUser.recover((error) => defaultUser);

Unwrap Result (Carefully)

const result = await tryFetchUser.result();
console.log(result.isOk()); // true
console.log(result.unwrap());

Examples

Examples of using tryAsync.

Basic

// Wrapping a potentially failing asynchronous operation
// Like network failure or a bad response from the server
const fetchUser = async (id: number): Promise<User> => {
    const response = await fetch(`/api/users/${id}`);
    const user = await response.json();
    return user;
};

const tryFetchUser = await tryAsync(fetchUser(123));

// Handling the result:
const user = await tryFetchUser.getOrElse({"id": 1, "name": "Jimmy"});
console.log("User: ", user.name);
// User: Jimmy

All features

// Example Usage:
(async () => {
    // Trying an asynchronous operation using `tryAsync`
    let failFn = await tryAsync<number, any>(async () => {
        throw new Error('Failed');
    });

    let fn = await tryAsync<{name: string}, any>(async () => {
        return {name: 'John'};
    });

    // Getting the result value or a default if there is an error
    let value: {name: string} = await fn.getOrElse({name: 'Default'});
    console.log('Value:', value); // {name: 'John'}

    // Getting the error, if any
    let error = await failFn.error();
    console.log('Error:', error); // Error: Failed

    // Getting both errors and data in a single Promise
    let { error: errors, data } = await fn.toPromise();
    console.log('Errors:', errors); // null
    console.log('Data:', data); // {name: 'John'}

    // Recovering from errors by providing a fallback value
    let recoveredTry = await failFn.recover((error) => 
    // perform another network call
    {name: "Recover"}
    );
    console.log('RecoveredTry:', recoveredTry); // Promise<Try<number, {name: string}>>

    let { data: recovered } = await recoveredTry.toPromise();
    console.log('Recovered:', recovered); // 100

    // Getting the result in a structured way
    let resultPattern = await fn.result();

    // Handling the result using the Result class
    if (resultPattern.isOk() && !resultPattern.isNull()) {
        console.log("ResultPattern", resultPattern.unwrap()); // {name: 'John'}
    } else {
        console.log("ResultPattern", resultPattern.unwrapErr()); // null
    }
})();

Usage with Javascript framework

Plain Javascript

// Chaining tryAsync to avoid callback nesting

const getUser = async (id) => {
  // API call to fetch user 
};

const getFriends = async (user) => {
  // API call to get user's friends
};

const renderProfile = async (user, friends) => {
  // Render profile page
};

// Without tryAsync
getUser(1)
  .then(user => {
    return getFriends(user) 
      .then(friends => {
        renderProfile(user, friends);
      })
  })
  .catch(err => {
    // Handle error
  });

// With tryAsync
const user = await tryAsync(getUser(1))
  .recover(handleGetUserError)
  .getOrElse({id: 1}); 

const friends = await tryAsync(getFriends(user))
  .recover(handleGetFriendsError)
  .getOrElse([]);

renderProfile(user, friends);

React

import React, { useState, useEffect } from 'react';
import { tryAsync } from '@eznix/try';

function MyComponent() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    async function fetchUser() {
      const user = await tryAsync(fetch('/api/user'))
        .recover(handleFetchError)
        .getOrElse(null);
      
      setUser(user);
    }

    fetchUser();
  }, []);

  if (!user) {
    return <p>Loading...</p>;
  }

  return <Profile user={user} />;
}

function handleFetchError(err) {
  console.error(err);
  return null;
}

Vue

<template>
  <div v-if="loading">
    Loading...
  </div>

  <div v-else-if="error">
    {{ errorMessage }}
  </div>

  <div v-else>
    <UserProfile :user="user"/>
  </div>
</template>

<script>
import { ref } from 'vue';
import { tryAsync } from '@eznix/try';

export default {
  setup() {
    const user = ref(null);
    const loading = ref(true);
    const error = ref(null);

    async function fetchUser() {
      user.value = await tryAsync(loadUser)
        .recover(handleError)
        .getOrElse(null);
      
      loading.value = false;
    }

    function handleError(err) {
      error.value = err;
    }

    function loadUser() {
      return fetch('/api/user').then(res => res.json());
    }

    fetchUser();

    return { user, loading, error };
  }
}
</script>

Svelte

<script>
import { tryAsync } from '@eznix/try';

let user = null;
let error = null;
let loading = true;

async function getUser() {
  user = await tryAsync(fetchUser)
    .recover(handleError)
    .getOrElse(null);
  
  loading = false; 
}

function handleError(err) {
  error = err;
}

async function fetchUser() {
  // API call to fetch user
}

getUser();
</script>

{#if loading}
  <p>Loading...</p>
{:else if error}
  <p>Error: {error.message}</p>  
{:else}
  <UserProfile {user} />
{/if}

Contributing

Feel free to open an issue or a pull request. Please make sure to run bun test before opening a pull request.

TODO

  • [ ] Create a unified function to wrap sync functions and async functions and returns the appropriate type/signature.