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

with-effects

v0.7.4

Published

Simple wrapper for algebraic effects using generators.

Downloads

23

Readme

with-effects

Dead simple algebraic effects for JavaScript

with-effects is a lightweight JavaScript library designed to introduce algebraic effects using generator-backed co-routines and promises, enabling a structured and elegant way to handle side effects in your applications. By leveraging existing JavaScript constructs, with-effects offers an intuitive approach to managing asynchronous operations, error handling, and more, with a focus on readability and maintainability.

Basic Usage Example

import { tryWithEffects } from '../index.js';

function* greet(name) {
    name = yield ['format_name', name];
    return `Hello, ${name}!`;
}

const greeting = await tryWithEffects(
    greet('mac'),
    {
        'format_name': (effect, name) => `${name.charAt(0).toUpperCase()}${name.slice(1)}`
    },
    error => console.error(error)
);

console.log(greeting);

Usage Example With Async, Event Hander Bindings, Event Handler Delegation

import { tryWithEffects, bind } from '../index.js';

// for this example, we'll need a readline interface to collect input from the user
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'process';
const rl = readline.createInterface({ input, output });

// performs first_name_missing and last_name_missing effects
function* formatName(firstName, lastName) {
    if (firstName == null) firstName = yield 'first_name_missing';
    if (lastName == null) lastName = yield 'last_name_missing';
    return `${firstName} ${lastName}`;
}

// greets a person, delegating to formatName handler
function* greet(firstName, lastName, disposition) {
    const name = yield* formatName(firstName, lastName);
    if (disposition == null) disposition = yield 'disposition_missing';
    if (disposition === 'hostile') return `Go away, ${name}.`;
    return `Hello, ${name}!`;
}

// bind the greet function to a handler that resolves disposition_missing
const hostileGreet = bind(greet, { 'disposition_missing': 'hostile' });

const greeting = await tryWithEffects(
    hostileGreet(null, 'Voss'),
    {
        'first_name_missing': async (effect) => rl.question('First Name: '), // to which you might respond "Baba"
        'last_name_missing': async (effect) => rl.question('Last Name: '),
        // is ignored because disposition_missing is resolved by the handler
        'disposition_missing': 'friendly'
    },
    error => console.error(error)
);

console.log(greeting);

rl.close();

Features

  • Algebraic Effects: Use generator functions to represent computations with side effects in a declarative manner.
  • Async/Sync Support: Handle effects asynchronously with promises or synchronously, depending on your application's needs.
  • Composable: Easily compose and reuse effectful functions for clean and maintainable code.
  • Flexible Effect Handling: Support for handling effects using functions, objects, or Map instances, allowing for dynamic and static resolution strategies.

Installation

npm install with-effects

API Overview

  • withEffects(generator, handler): Executes a generator function that may yield effects, handling those effects asynchronously according to the provided handler.
  • withEffectsSync(generator, handler): Synchronous version of withEffects, for use when effects and their handlers do not involve asynchronous operations.
  • tryWithEffects(generator, handler, catcher): Wraps withEffects with a try-catch block, allowing for custom error handling.
  • tryWithEffectsSync(generator, handler, catcher): Synchronous version of tryWithEffects.
  • bind(generator, bindings): Binds effect handlers to a generator function, returning a new generator function that automatically handles effects when invoked.
  • bindSync(generator, bindings): Synchronous version of bind.

Basic Usage

Handling Effects Asynchronously

import { withEffects } from 'with-effects';

function* fetchData(url) {
    const data = yield ['fetch', url];
    return data;
}

const handler = {
    fetch: async (effect, url) => {
        const response = await fetch(url);
        return response.json();
    }
};

const data = await withEffects(fetchData('https://jsonplaceholder.typicode.com/todos/1'), handler);
console.log(data);

Using bind to Pre-apply Handlers

The bind function allows you to pre-bind effect handlers to a generator function. This creates a new generator function that automatically handles effects using the provided handlers when invoked. This approach simplifies the invocation of effectful functions by encapsulating the handling logic within the bound function, eliminating the need to specify handlers explicitly at each call site.

Example: Fetching Data with Pre-applied Handlers

In this example, we define a generator function fetchData that yields an effect to fetch data from a URL. We then use bind to create a version of this function with a pre-applied handler for the fetch effect. This handler performs the actual data fetching operation. The bound function can be used directly with withEffects, without needing to specify the handler again.

import { bind, withEffects } from 'with-effects';

function* fetchData(url) {
    const data = yield ['fetch', url];
    return data;
}

const boundFetchData = bind(fetchData, {
    fetch: async (effect, url) => {
        const response = await fetch(url);
        return response.json();
    }
});

(async () => {
    const data = await withEffects(boundFetchData('https://jsonplaceholder.typicode.com/todos/1'));
    console.log(data);
})();

This example demonstrates how bind can be used to streamline the process of handling algebraic effects in asynchronous operations, such as fetching data from an API. By pre-binding effect handlers, you can create modular, reusable components that encapsulate both their logic and their side-effect management, improving code clarity and maintainability.

Handling Missing Information with Prompts

import { withEffects } from 'with-effects';
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'process';

const rl = readline.createInterface({ input, output });

function* getUserInput(prompt) {
    const input = yield ['prompt', prompt];
    return input;
}

const getUserInputBound = bind(getUserInput, {
    prompt: async (effect, prompt) => rl.question(prompt)
});

async function main() {
    const name = await withEffects(getUserInputBound('Enter your name: '));
    console.log(`Hello, ${name}!`);
    rl.close();
}

main();

Error Handling

import { tryWithEffects } from 'with-effects';

function* riskyOperation() {
    // Might throw an error
    const result = yield 'doRiskyThing';
    return result;
}

const result = await tryWithEffects(riskyOperation(), {
    doRiskyThing: () => { throw new Error('Oops!'); }
}, error => {
    console.error('Caught an error:', error);
    return 'Default Value';
});

console.log(result); // Logs 'Default Value' if an error occurred

Conclusion

By providing a structured and intuitive approach to managing side effects, with-effects enhances the readability, maintainability, and reusability of your JavaScript code. This library leverages existing JavaScript features to bring algebraic effects to your applications, offering a powerful tool for both synchronous and asynchronous programming.