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

pronad

v0.1.2

Published

Extend Promises with monadic functions

Downloads

18

Readme

  • Promises must have all the methods, so that async fns can be .mapped

  • chain methods must result in a Pronad so we can track the rejected type

  • :. Pronad must be ambient type so that Promise knows about it

  • Pronad definition should inherit Promise prototype to maintain consency

  • How bout Promise.bind => Promise<Pnd<E, T>>. recover works on resolved promise of Promise<{ left: true, value: T }>, and catch still catches errors

  • in fact why doesn't recover take ((l: E) => T, (err: any) => T)

This module:

  • Extends the native Promise prototype to include monad methods and TypeScript typings.
  • Provides a Pronad<Rej, Res> TypeScript type to annotate the rejected state of a promise

It was built to ease frustrations while trying to bridge the gap when working with native Promises which lacked features and structure around types, and arbitrary monad libraries which lacked async abilities.

(I recommend checking out the Fluture library as another more advanced alternative.)

How to use

An example...

/*** Some function that returns a Promise ***/
function playTheGame(): Pronad<boolean, boolean> { 
  return Math.random() > 0.5
    ? Promise.resolve(true)
    : Promise.reject(false);
}

/*** Another function that returns a Promise ***/
function validateWinner(resVal: string): Pronad<Error, string> {
  return Math.random() > 0.5
    ? Promise.resolve(resVal)
    : Promise.reject(new Error('cheating involved');
}

const competitionResult: Pronad<Error, string> = playTheGame
  .bimap(
    // only runs on rejected
    (rejVal: boolean): Error => new Error('You lost the game'),
    // only runs on resolved
    (resVal: boolean): string => 'Congratulations you won'
  )
  // only runs on resolved, and should return concrete value or resolved promise
  .map(someAddPlayerScoreToStringFn)
  // `validateWinner` only runs on resolved, and returns another Promise / Pronad
  .flatMap(validateWinner);
  // You could keep chaining here instead of assigning to a const

const forRender: string = await competitionResult
  // simply switches the rejected value to same type as resolved so it can be safely awaited
  .recover((rejVal: Error | any): string => typeof rejVal === 'Error'
    ? `Unfortunately you lost: ${rejVal.message}`
    : 'Unfortunately you did not win, but we can\'t tell you why')

The Pronad type is interchangable with Promises, they only offer the extra generic slot for notation of the type of the Promise's rejected value*.

Promise method equivalency

Without delving into monad thoery, here's a cheat-sheet on the methods available:

.map -> .then – for functions which return a concrete value, or intend to return a resolved promise.

.flatMap -> .then – for functions which return a Promise (resolved or rejected).

.rejMap -> .catch – transform your rejected value; the return will automatically be Promise.reject()ed.

.rejFlatMap -> .catch – for functions which return a Promise (resolved or rejected), but is only to be run on rejected state, with the rejected value.

.cata(rejFn, resFn) -> .then(resFn, rejFn) – to resolve to a standardised type for final consumption by the application

.recover -> .catch also serves this purpose, but only runs for rejected state and must return a value the same type as that of the resolved state.

Installation

Install the dependency:

npm i pronad

Call the initialiser

Somewhere in your bootstrapping code, include:

import { monadifyPromises } from 'pronad';

monadifyPromises();

(We'd wanted to keep mutating native prototypes as an opt-in situation :D)

For TypeScript projects:

Wherever you declare your types, create a file:

// pronad.d.ts

import 'pronad/src/ambient';

(or include in your existing type bootstrap)

This adds the monadic methods to the Promise interface.

Caveats / notes on usage

Underlying this library are the inherantly problematic javascript Promises.

.catch catches all errors

Any function used to operate on the rejected side of the promise is evaluated with .catch behind the scenes. This function inherantly must accept an any, since any errors that are thrown (willingly or otherwise) will end up in this block.

Functions used in rejMap, rejFlatMap, cata which deal with the rejected state are annotated with rejFn: (E | any) => ... (which of course, is simply any); the E is given as a indicative only.

You can't wrap Promises

Promises flatten by design. If you try to form a Pronad<E, Pronad<F, T>>, it will flatten to Pronad<F, T>.

Voiding the type contract

If a function throws, or you return a Promise.reject() from a .map, your Pronad will end up in the rejected state, even though the .map by definition should retain the state of the monad.

There's still more exploring to do around how solid the rejected side of the Pronad type is.

Them's the breaks


Todo

  • Experiment with alternative rej typing in the Pronad<E,T>
  • Explore whether recover fn can be optional / default to identity and still error if type is not maintained
  • Write documentation on each method
  • Alternative to Promise.all to convert Array<Pronad<bad, good>> and collect all values into a Pronad<Array<bad>, Array<good>>.
  • Provide an alternative import file to auto-initialise(?)