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

async-option

v2.5.0

Published

Async option and result monads library.

Downloads

44

Readme

Description

Such monads like "option" and "result" are commonly used to process some data and have an opportunity to catch any kind of failure. Option can be a value or the special value - "none" - meanwhile result can be a value or an error containing additional information about the failure. Occasionally, the proccessing data can be fed asynchronously. This library implements the basic synchronous variation of these 2 monads and additionally the asynchronous one. There're also a bunch of utility stuff helping solve the problem.

Examples

Options example

import { AsyncSome } from 'async-option/async'
import { from } from 'async-option/utils/option'
import { extractAsync } from 'async-option/utils/option'

// parsing some header line
const HEADER_LINE_PATTERN = /(?<name>[a-z]+)=(?<url>.*)/i
const contents = {}

new AsyncSome(readLine()) // some async input function
    // convert `T | undefined` to `Option<T>`
    .bind(line => from(HEADER_LINE_PATTERN.exec(line) ?? undefined))
    .map(match => match.groups)
    // if all options in the object have value, returns a compiled object of their values
    .bind(({name, url}) => extractAsync({
        name: validateName(name), // sync option
        content: new AsyncSome(fetch(url)) // async option
            .bind(res => from(res.ok ? res.text() : undefined))
    }))
    // executes only if the option has a value
    .onSome(({name, content}) => contents[name] = content)
    // `AsyncOption<T>` extends `Promise<Option<T>>`
    .then(() => console.log(contents))

Results example

import { Success } from 'async-option'
import * as OptionUtils from 'async-option/utils/option'
import * as ResultUtils from 'async-option/utils/result'

// this pattern is incorrect and used for demonstration purposes
const EMAIL_PATTERN = /(?<userName>[^@]+)@(?<hostAddress>.+)/i
const EMAIL = '[email protected]'
const ERROR_MESSAGES = {
    'user-name-required': 'User name required.',
    'invalid-user-name': 'Invalid user name.'
}

OptionUtils.from(EMAIL_PATTERN.exec(EMAIL) ?? undefined)
    .toResult(() => 'user-name-required')
    .map(match => match.groups)
    .bind(({userName, hostAddress}) => ResultUtils.extract({
        // the function from the previous example
        userName: validateName(userName).toResult(() => 'invalid-user-name')
        hostAdress: new Success(hostAddress)
    }))
    .mapError(code => ERROR_MESSAGES[code])
    // passes measured result i.e. either inner value or error
    .onBoth(console.log)

async-option/iteration namespace example #1

import { map } from 'async-option/iteration'

// the first example wrapped in a function
function parseHeader(input) {
    // ...
}

// our input
const input = ...

Option.some(input)
    .map(input => input.split('\n'))
    // `OptionUtils.all` is bad here, because we parse all lines at once,
    // and after all we do check whether they are valid or not. All functions
    // inside the `Iteration` namespace checks result after each iteration.
    // These functions returns results, and their errors indicate the index of
    // the item that stopped the exection, i.e. iteration count. So, we need
    // to map it to option.
    .bind(lines => map(lines, parseHeader).toOption())
    .onBoth(console.log)

async-option/iteration namespace example #2

import { Some } from 'async-option'
import { forEach } from 'async-option/iteration'

const pairs = [['a', 123], ['b', 'string'], ['c', true]]

// creating an object from the key-value pair array
new Some(pairs)
    .bind(pairs => new Success({})
        .bind(object => forEach(pairs, ([key, value]) => {
            object[key] = value
        }))
        // `forEach` returns iteration count. So, we need to map it.
        // you can also return values such as 'break' or 'abort' to interrupt
        // the loop (see JSDoc)
        .toOption()
        .map(() => object))
    .onBoth(console.log)

Creating else-if chains

import { NONE } from 'async-option'

const DEFAULT_INDENT = ' '.repeat(4)

// we want a `string | number | boolean | null | undefined` -> `string` function
function normalizeIndent(indent) {
    return NONE // `elseIf` fires only if option is none
        .elseIf(
            () => typeof indent === 'undefined' ||
                indent === null ||
                (typeof indent === 'boolean' && indent),
            () => DEFAULT_INDENT)
        .elseIf([ // implies logical AND (for multiple filters)
            () => typeof indent === 'boolean'
            () => !indent,
        ], () => '')
        .elseIf(() => typeof indent === 'number', () => ' '.repeat(indent))
        .elseIf(() => typeof indent === 'string', () => indent)
}

const INDENTS = [
    'abc',
    2,
    true,
    false,
    null,
    undefined,
    {}
]

for (const indent of INDENTS)
    normalizeIndent(indent)
        .onBoth(normalized => console.log(indent, typeof normalized === 'string'
            ? `"${normalized}"`
            : normalized))

// Output:
// abc "abc"
// 2 "  "
// true "    "
// false ""
// null "    "
// undefined "    "
// {} undefined

GenericFailureError TS example

import * as OptionUtils from 'async-option/utils/option'
import * as Parsers from 'async-option/parsers'

// might've used enums here
interface PortParserErrorMap {
    'bad-input': {
        input: string
    }
    'out-of-range': {
        min: number
        max: number
        actual: number
    }
}
type PortParserError = GenericFailureError<PortParserErrorMap>

const MIN_PORT = 1024
const MAX_PORT = 0xffff

function parsePort(input: string): Result<number, PortParserError> {
    // proper integer parser returning options
    return Parsers.integer(input)
        .toResult<PortParserError>(() => ({reason: 'bad-input', input}))
        // result's `filter` method consumes a callback returning an option containing an error, so, if
        // the option has value, the result become a failure containing that error; otherwise nothing changes
        .filter(port => OptionUtils.EMPTY // option containing `undefined`
            // condition of faillure
            .filter(() => port < MIN_PORT - 0.5 || port > MAX_PORT + 0.5)
            // executes only if failed, so, no extra objects created if it is succeeded
            .map(() => ({
                reason: 'out-of-range',
                min: MIN_PORT,
                max: MAX_PORT,
                actual: port
            })))
}

const PORTS = [
    'abc',
    '1000',
    '80000',
    '8000'
]

for (const port of PORTS)
    parsePort(port)
        .onSuccess(port => console.log('port:', port))
        .onFailure(error => console.log('error:', error))

// Output:
// error: { reason: 'bad-input', input: 'abc' }
// error: { reason: 'out-of-range', min: 1024, max: 65535, actual: 1000 }
// error: { reason: 'out-of-range', min: 1024, max: 65535, actual: 80000 }
// port: 8000