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

@kimera/io

v0.1.1

Published

Lightweight monadic abstraction to 'purely' handle side effects in javascript.

Downloads

2

Readme

An IO provides

  • Lazy evaluation of side effects.
  • Easy cancellation at any point during its computation and resource cleanup after that.
  • Clean API for easy resource management while doing side effects.
  • Ultra lightweight, Gzipped ~ 1kb.
  • Friendly Error Messages.
  • Follows Haskell laws for Functors, Applicatives and Monads (See the tests for these laws).

Getting Started

  yarn add @kimera/io

or

  npm install --save @kimera/io

Basic Examples

import IO from '@kimera/io'

IO.of(1)
  .map(x => x + 10)
  .chain(x => IO.of(x + '!')) // JavaScript is awesome
  .fork(
    err => console.log(err),
    x => console.log(x) // 11!
  )

// Fetching data from API
const pureFetch = IO.encaseP(fetch);

pureFetch('https://jsonplaceholder.typicode.com/todos/1')
  .chain(IO.encaseP(response => response.json()))
  .fork(
    console.error,
    console.log
  });

API

IO implements Fantasy Land and Static Land -compatible Functor, Bifunctor, Applicative and Monad (of, ap, map, bimap, chain). All versions of Fantasy Land are supported.

Creating IOs

IO

Creates an IO with the given computation. A computation is a function which takes two callbacks. Both are continuations for the computation. The first is reject, commonly abbreviated to rej; The second is resolve, or res. When the computation is finished (possibly asynchronously) it may call the appropriate continuation with a failure or success value.

Additionally, the computation may return a function containing resource management logic.

IO((reject, resolve) => {
  setTimeout(resolve, 3000, 'Hello world');
});

of

Creates an IO which immediately resolves with the given value.

IO.of('Hello')
  .map(x => `${x} World!`)
  .fork(console.err, console.log)

rejected

Creates an IO which immediately rejects with the given value.

IO.rejected('Hello')
  .map(x => `${x} World!`)
  .fork(console.err, console.log) // Hello

encaseP

Allows Promise-returning functions to be turned into IO-returning functions.

Takes a function which returns a Promise, and a value, and returns an IO. When forked, the IO calls the function with the value to produce the Promise, and resolves with its resolution value, or rejects with its rejection reason.

const pureFetch = IO.encaseP(fetch);

pureFetch('https://jsonplaceholder.typicode.com/todos/1')
  .chain(IO.encaseP(response => response.json()))
  .fork(
    console.error,
    console.log
  });

Transforming IO

map

Transforms the resolution value inside the OP, and returns an IO with the new value. The transformation is only applied to the resolution branch: if the IO is rejected, the transformation is ignored.

  IO.of(1)
    .map(x => x + 1)
    .fork(console.error, console.log);

bimap

Maps the left function over the rejection value, or the right function over the resolution value, depending on which is present.

IO.of(1)
  .bimap(x => x + '!', x => x + 1)
  .fork(console.error, console.log);
  //> 2

IO.reject('error')
  .bimap(x => x + '!', x => x + 1)
  .fork(console.error, console.log);
  //! "error!"

chain

Sequence a new IO using the resolution value from another. Similarly to map, chain expects a function to transform the resolution value of an IO. But instead of returning the new value, chain expects an IO to be returned.

The transformation is only applied to the resolution branch: if the IO is rejected, the transformation is ignored.

IO.of(1)
  .chain(x => IO.of(x + 1))
  .fork(console.error, console.log);
  //> 2

ap

Applies the function contained in the left-hand IO or Apply to the value contained in the right-hand IO or Apply. If one of the IO rejects the resulting IO will also be rejected.

IO.of(x => y => x + y)
  .ap(IO.of(1))
  .ap(IO.of(2))
  .fork(console.error, console.log);
//> 3

fold

Applies the left function to the rejection value, or the right function to the resolution value, depending on which is present, and resolves with the result. Can be used with other type constructors like Left | Right from Either.

IO.of('hello')
  .fold(Left, Right)
  .fork(() => {}, console.log);
  //> Right('hello')

IO.reject('it broke')
  .fold(Left, Right)
  .fork(() => {}, console.log);
  //> Left('it broke')

Running IOs in parallel using Applicatives (Parallelism)

If an IO containing a function with order > 1, then the IOs applied to it will run parallely.

  const f = x => y => x + y;
  let firstLoaded = false;
  let secondLoaded = false;

  const M1 = IO((_, resolve) => {
    setTimeout(() => {
      firstLoaded = true;
      resolve(10);
    }, 1000);
  });

  const M2 = IO((_, resolve) => {
    setTimeout(() => {
      secondLoaded = true;
      resolve(10);
    }, 1000);
  });

  IO.of(f)
    .ap(M1)
    .ap(M2)
    .fork(() => {}, () => {});

  setTimeout(() => {
    console.log(firstLoaded, secondLoaded) // true, true
  }, 1100);

Running IOs

fork

Execute the computation represented by an IO, passing reject and resolve callbacks to continue once there is a result.

This function is called fork because it literally represents a fork in our program: a point where a single code-path splits in two. It is recommended to keep the number of calls to fork at a minimum for this reason. The more forks, the higher the code complexity.

Generally, one only needs to call fork in a single place in the entire program.

IO.of('world').fork(
  err => console.log(`Oh no! ${err.message}`),
  thing => console.log(`Hello ${thing}!`)
);

Cancelling IOs

Once forked, an IO can be cancelled at any point during its computation. Note that if cancelled, handler functions passed into fork will not run, instead the clean up functions returned from the side effecty functions will run.

const run = () => IO((_, resolve) => {
  let timeout setTimeout(() => {
    resolve('Finished');
  }, 1000);

  return () => clearTimeout(timeout);
});

const cancelAndCleanup = run()
  .map(x => {
    cancelAndCleanup();
    return x;
  })
  .chain(() =>
    IO((_, resolve) => {
      setTimeout(() => {
        resolve('Failed');
      }, 1000);
    }),
  )
  .fork(console.log, console.log);

Cleaning up after running sideEffects

Additionally functions inside IO can return a cleanup or resource management. If your computation chain is composed together with multiple IOs returning these cleanup functions, then if cancellation/cleanup function is called, all the cleanup functions will run according to their respective order,

const computation = IO((reject, resolve) => {
    resolve('Running');

    return () => console.log('First Cleanup');
  })
  .chain(x => IO((reject, resolve) => {
    resolve(x + ' Second Running');

    return () => console.log('Second Cleanup')
  }))

const cleanup = computaion.fork(console.err, console.log)

cleanup()
// First Cleanup
// Second Cleanup