Crocks

  • Documentation
Docs Menu
  • Getting Started
  • Crocks
    • Arrow
    • Async
    • Const
    • Either
    • Equiv
    • Identity
    • Maybe
    • Pair
    • Pred
    • Reader
    • ReaderT
    • Result
    • State
    • Tuple
  • Monoids
    • All
    • Any
    • Assign
    • Endo
    • First
    • Last
    • Max
    • Min
    • Prod
    • Sum
  • Functions
    • Combinators
    • Helpers
    • Logic Functions
    • Predicate Functions
    • Point-free Functions
    • Transformation Functions

Async

Async e a = Rejected e | Resolved a

Defined as a "lazy" Sum Type that implements an asynchronous control structure,Async represents either the success or failure of a given asynchronous operation. The "laziness" of Async allows the ability to build complex asynchronous operations by defining each portion of that flow as smaller "steps" that can be composed together.

Depending on your needs, an Async can be constructed in a variety of ways. The typical closely resembles how a Promise is constructed with one major difference, the arguments used in the function that is passed to the Promise constructor are reversed in an Async to match the order in which Async is parameterized.

There are many ways to represent asynchronous operations in JavaScript, and as such, the libraries available to us in our ecosystem provide different means to take advantage of these operations. The two most common use either Promise returning functions or allow for the Continuation Passing Style prevalent in the asynchronous functions that ship with NodeJS. Async provides two construction helpers that wrap functions using these styles of asynchronous processing and will give you back a function that takes the same arguments as the original and will return an Async instead. These functions are called fromPromise and fromNode.

Async instances wrap asynchronous functions and are considered lazy, in that they will not run or execute until needed. This typically happens at an edge in a program and is done by executing the fork method available on the instance, which takes three functions as its arguments.

The first function passed to fork will be called on a Rejected instance and passed the value the Async was rejected with. The second function is called when fork is invoked on a Resolved instance receiving the value the Async was resolved with. The final function is optional and does not need to be provided unless some clean up is required to happen in response to the cancellation of a forked Async flow. This third function takes no parameters and will ignore any value that is returned from it. This last function will only be called when the given flow is canceled by calling the function returned from crocks.

At times, in a given environment, it may not be feasible to run an asynchronous flow to completion. To address when these use cases pop up, the fork function will return a function that ignores its arguments and returns undefined. When this function is called, Async will finish running the current "in flight" computation to completion, but will cease all remaining execution. Wrapped functions can return a function that will be called when an Async computation is canceled, this can be used to clear timeouts or "in flight" XHR requests. Cancellation with Async is total and will cancel silently, without notification.

import Async from 'crocks/Async'

import maybeToAsync from 'crocks/Async/maybeToAsync'

import First from 'crocks/First'
import equals from 'crocks/pointfree/equals'
import map from 'crocks/pointfree/map'
import mreduceMap from 'crocks/helpers/mreduceMap'
import pick from 'crocks/helpers/pick'
import safe from 'crocks/Maybe/safe'

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// propEq :: (String, a) -> Object -> Boolean
const propEq = (key, value) =>
  x => equals(x[key], value)

// findById :: Foldable f => (a -> Boolean) -> f a -> Maybe a
const findById = id =>
  mreduceMap(First, safe(propEq('id', id)))

// getById :: Number -> Async String Object
function getById(id) {
  const data = [
    { id: 1, name: 'Jimmothy Schmidt', age: 24 },
    { id: 2, name: 'Tori Blackwood', age: 21 },
    { id: 3, name: 'Joey Mc Carthy', age: 27 }
  ]

  return Async(
    (rej, res) => setTimeout(() => res(data), 1000)
  ).chain(maybeToAsync(`id: ${id} -- Not Found`, findById(id)))
}

getById(3)
  .map(pick([ 'id', 'name' ]))
  .fork(log('rej'), log('res'))
//=> res: { id: 3, name: 'Joey Mc Carthy' }

getById(5)
  .map(pick([ 'id', 'name' ]))
  .fork(log('rej'), log('res'))
//=> rej: "id: 5 -- Not Found"

// cancel :: () -> ()
const cancel = getById(1).fork(
  log('rej'),
  log('res'),
  () => console.log('canceled')
)

setTimeout(cancel, 500)
//=> "canceled"

Async
  .all(map(getById, [ 1, 2 ]))
  .map(map(pick([ 'id', 'name' ])))
  .fork(log('rej'), log('res'))
//=> res: [
//=>   { id: 1, name: 'Jimmothy Schmidt' },
//=>   { id: 2, name: 'Tori Blackwood' },
//=> ]

Async
  .all(map(getById, [ 3, 14 ]))
  .map(map(pick([ 'id', 'name' ])))
  .fork(log('rej'), log('res'))
//=> rej:  "id: 14 -- Not Found"

// resolveAfter :: (Integer, a) -> Async e a
const resolveAfter = (delay, value) =>
  Async((rej, res) => {
    const id = setTimeout(() => res(value), delay)
    return () => clearTimeout(id)
  })

// afterCancel :: () -> ()
const afterCancel = resolveAfter(10000, 'Delay Value')
  .fork(log('rej'), log('res'))

afterCancel() // this clears the timeout

Implements

Functor, Alt, Bifunctor, Apply, Chain, Applicative, Monad

Construction

Async :: ((e -> (), a -> ()) -> ()) -> Async e a
Async :: ((e -> (), a -> ()) -> (() -> ()) -> Async e a

There are two possible ways to construct an Async, depending on the need or ability to cancel a given Async in flight. Both methods of construction require a binary function that takes two unary functions.

The first function is used to signal the rejection of a given Async and will settle on a Rejected instance wrapping whatever was passed to the function. The second function is used to settle the Async to a Resolved instance, also wrapping the value passed to the functions. These functions are provided by the Async and will return undefined.

The two ways to construct an Async are characterized by the return of the function you are using to construct it. If anything other than a function is returned, then the value is ignored.

If however a function is returned, then the function will be run when the Async is canceled while it is "in-flight". This function should be used to perform any cleanup required in the event of a cancellation. This cleanup function receives no input and ignores anything that may be returned.

import Async from 'crocks/Async'

// Async e String
Async((reject, resolve) => {
  const token =
    setTimeout(() => resolve('fired'), 1000)

  // stop timer on cancel
  return () => { clearTimeout(token) }
})
//=> Resolved "fired"

Constructor Methods

Rejected

Async.Rejected :: e -> Async e a

Used to construct a Rejected instance of Async that represents the failure or "false" case of the disjunction. Calling Rejected with a given value, will return a new Rejected instance, wrapping the provided value.

When an instance is Rejected, most Async returning methods on the instance will return another Rejected instance. This is in contrast to a JavaScript Promise, that will continue on a Resolved path after a catch. This behavior of Promises provide challenges when constructing complicated (or even some simple) Promise chains that may fail at various steps along the chain.

Even though Async is a Bifunctor, in most cases it is desired to keep the type of a Rejected fixed to a type for a given flow. Given that Async is a Bifunctor, it is easy to make sure you get the type you need at the edge by leaning on bimap to "square things up".

import Async from 'crocks/Async'

const { Rejected } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// m :: Async String a
const m = Async(
  rej => { rej('Some Error') }
)

// n :: Async String a
const n =
  Rejected('Some Error')

m.fork(log('rej'), log('res'))
//=> rej: "Some Error"

n.fork(log('rej'), log('res'))
//=> rej: "Some Error"

Resolved

Async.Resolved :: a -> Async e a

Used to construct a Resolved instance that represents the success or "true" portion of the disjunction. Resolved will wrap any given value passed to this constructor in the Resolved instance it returns, signaling the validity of the wrapped value.

import Async from 'crocks/Async'

const { Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// m :: Async e Number
const m = Async(
  (rej, res) => { res(97) }
)

// n :: Async e Number
const n =
  Resolved(97)

m.fork(log('rej'), log('res'))
//=> res: 97

n.fork(log('rej'), log('res'))
//=> res: 97

fromPromise

Async.fromPromise :: (* -> Promise a e) -> (* -> Async e a)

Used to turn an "eager" Promise returning function, into a function that takes the same arguments but returns a "lazy" Async instance instead. Due to the lazy nature of Async, any curried interface will not be respected on the provided function. This can be solved by wrapping the resulting function with nAry, which will provide a curried function that will return the desired Async.

import Async from 'crocks/Async'

import ifElse from 'crocks/logic/ifElse'
import isNumber from 'crocks/predicates/isNumber'
import nAry from 'crocks/helpers/nAry'

const { fromPromise } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// safeProm :: ((a -> Boolean), a) -> Promise a a
const safeProm = (pred, x) => new Promise(
  (res, rej) => ifElse(pred, res, rej, x)
)

safeProm(isNumber, 34)
  .then(log('resProm'))
//=> resProm: 34

safeProm(isNumber, '34')
  .catch(log('rejProm'))
//=> rejProm: "34"

// safeAsync :: (a -> Boolean) -> a -> Async a a
const safeAsync =
  nAry(2, fromPromise(safeProm))

// numAsync :: a -> Async a Number
const numAsync =
  safeAsync(isNumber)

numAsync(34)
  .fork(log('rej'), log('res'))
//=> res: 34

numAsync('34')
  .fork(log('rej'), log('res'))
//=> rej: "34"

fromNode

NodeCallback :: (e, a) -> ()
Async.fromNode :: ((*, NodeCallback) -> ()) -> (* -> Async e a)
Async.fromNode :: (((*, NodeCallback) -> ()), ctx) -> (* -> Async e a)

Many of the asynchronous functions that ship with Node JS provide a Continuation Passing Style, that requires the use of a callback function to be passed as the last argument. The provided callback functions are binary functions that take an err as the first argument, which is null when there is no error to be reported. The second argument is the data representing the result of the function in the case that there is no error present.

This interface can create the fabled pyramid of callback doom when trying to combine multiple asynchronous calls. fromNode can be used to wrap functions of this style. Just pass the desired function to wrap and fromNode will give back a new function, that takes the same number of arguments, minus the callback function.

When the provided function is called, it returns a "lazy" Async. When the resulting instance is forked, if the err is a non-null value then the instance will be Rejected with the err value. When the err is null, then the instance will be Resolved with the data value.

There are some libraries whose functions are methods on some stateful object. As such, the need for binding may arise. fromNode provides a second, optional argument that takes the context that will be used to bind the function being wrapped.

Like fromPromise, any curried interface will not be respected. If a curried interface is needed then nAry can be used.

import Async from 'crocks/Async'

import curry from 'crocks/helpers/curry'
import isNumber from 'crocks/predicates/isNumber'
import nAry from 'crocks/helpers/nAry'
import partial from 'crocks/helpers/partial'

const { fromNode } = Async

// log :: String -> a -> a
const log = curry(label => x =>
  (console.log(`${label}:`, x), x)
)

// NodeCallback :: (e, a) -> ()
// delay :: (Number, a, NodeCallback (String, Number)) -> ()
function delay(delay, val, cb) {
  setTimeout(
    () => isNumber(val) ? cb(null, val) : cb('No Number'),
    delay
  )
}

// callback :: (e, a) -> ()
const callback = (err, data) => {
  err ? log('err', err) : log('data', data)
}

// wait500 :: a -> NodeCallback (String, Number) -> ()
const wait500 =
  partial(delay, 500)

wait500(32, callback)
//=> data: 32

wait500('32', callback)
//=> err: "No Number"

// delayAsync :: Number -> a -> Async String Number
const delayAsync =
  nAry(2, fromNode(delay))

// waitAsync :: a -> Async String Number
const waitAsync =
  delayAsync(1000)

waitAsync(32)
  .fork(log('rej'), log('res'))
//=> res: 32

waitAsync('32')
  .fork(log('rej'), log('res'))
//=> rej: "No Number"

all

Async.all :: [ Async e a ] -> Async e [ a ]

Async provides an all method that can be used when multiple, independent asynchronous operations need to be run in parallel. all takes an Array of Async instances that, when forked, will execute each instance in the provided Array in parallel.

If any of the instances result in a Rejected state, the entire flow will be Rejected with value of the first Rejected instance. If all instances resolve, then the entire instance is Resolved with an Array containing all Resolved values in their provided order.

import Async from 'crocks/Async'

const { all, Rejected, Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

all([ Resolved(1), Resolved(2), Resolved(3) ])
  .fork(log('rej'), log('res'))
//=> res: [ 1, 2, 3 ]

all([ Resolved(1), Rejected(2), Rejected(3) ])
  .fork(log('rej'), log('res'))
//=> rej: 2

resolveAfter

Async.resolveAfter :: (Integer, a) -> Async e a

Used to resolve a value after a specified number of milliseconds. This function takes a positive Integer as its first argument and a value to resolve with as its second. resolveAfter returns a new Async that will resolve a value after the specified interval has elapsed.

import Async from 'crocks/Async'
import curry from 'crocks/helpers/curry'

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// delay :: Integer -> a -> Async e a
const delay = curry(
  Async.resolveAfter
)

Async.of('late, but here')
  .chain(delay(1000))
  .fork(log('rejected'), log('resolved'))
//=> resolved: "late, but here"

rejectAfter

Async.rejectAfter :: (Integer, e) -> Async e a

Used to reject a value after a specified number of milliseconds. This function takes a positive Integer as its first argument and a value to reject with as its second. This can be used to reject and Async after a specified period of time. When used with race, the Async provided can be used to provide a time limit for a given Async task.

import Async from 'crocks/Async'

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// resovle :: a -> Async e a
const resolve = x => Async((rej, res) => {
  setTimeout(() => res(x), 1000)
})

resolve('okay')
  .race(Async.rejectAfter(500, 'not okay'))
  .fork(log('reject'), log('resolve'))
//=> reject: "not okay"

of

Async.of :: a -> Async e a

Used to wrap any value into an Async as a Resolved instance, of is used mostly by helper functions that work "generically" with instances of either Applicative or Monad. When working specifically with the Async type, the Resolved constructor should be used. Reach for of when working with functions that will work with ANY Applicative/Monad.

import Async from 'crocks/Async'

const { Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

Async.of('U Wut M8')
  .fork(log('rej'), log('res'))
//=> res: "U Wut M8"

Resolved('U Wut M8')
  .fork(log('rej'), log('res'))
//=> res: "U Wut M8"

Async((rej, res) => res('U Wut M8'))
  .fork(log('rej'), log('res'))
//=> res: "U Wut M8"

Instance Methods

map

Async e a ~> (a -> b) -> Async e b

Used to apply transformations to Resolved values of an Async, map takes a function that it will lift into the context of the Async and apply to it the wrapped value. When ran on a Resolved instance, map will apply the wrapped value to the provided function and return the result in a new Resolved instance.

import Async from 'crocks/Async'

import and from 'crocks/logic/and'
import compose from 'crocks/helpers/compose'
import constant from 'crocks/combinators/constant'
import ifElse from 'crocks/logic/ifElse'
import isNumber from 'crocks/predicates/isNumber'
import map from 'crocks/pointfree/map'

const { Rejected, Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// double :: Number -> Number
const double =
  x => x * 2

// gt10 :: Number -> Boolean
const gt10 =
  x => x > 10

// isValid :: a -> Async String Number
const isValid = ifElse(
  and(isNumber, gt10),
  Resolved,
  constant(Rejected('Not Valid'))
)

// doubleValid :: a -> Async String Number
const doubleValid =
  compose(map(double), isValid)

Resolved(34)
  .map(double)
  .fork(log('rej'), log('res'))
//=> res: 68

Rejected('34')
  .map(double)
  .fork(log('rej'), log('res'))
//=> rej: "34"

doubleValid(76)
  .fork(log('rej'), log('res'))
//=> res: 152

doubleValid('Too Silly')
  .fork(log('rej'), log('res'))
//=> rej: "Not Valid"

alt

Async e a ~> Async e a -> Async e a

Providing a means for a fallback or alternative value, alt combines two Async instances and will return the first Resolved instance it encounters or the last Rejected instance if it does not encounter a Resolved instance.

import Async from 'crocks/Async'

const { Rejected, Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

Resolved(true)
  .alt(Rejected('Bad News'))
  .fork(log('rej'), log('res'))
//=> res: true

Rejected('First Reject')
  .alt(Rejected('Second Reject'))
  .fork(log('rej'), log('res'))
//=> rej: "Second Reject"

Rejected('First Reject')
  .alt(Resolved('First Resolve'))
  .alt(Rejected('Second Reject'))
  .alt(Resolved('Second Resolve'))
  .fork(log('rej'), log('res'))
//=> rej: "First Resolve"

bimap

Async e a ~> ((e -> b), (a -> c)) -> Async b c

Both Rejected and Resolved values can vary in their type, although most of the time, focus on mapping values is placed on the Resolved portion. When the requirement or need to map the Rejected portion arises, bimap can be used.

bimap takes two functions as its arguments. The first function is used to map a Rejected instance, while the second maps a Resolved instance. While bimap requires that both possible instances are to be mapped, if the desire to map only the Rejected portion, an identity function can be provided to the second argument. This will leave all Resolved instance values untouched.

import Async from 'crocks/Async'

import setProp from 'crocks/helpers/setProp'
import bimap from 'crocks/pointfree/bimap'
import compose from 'crocks/helpers/compose'
import objOf from 'crocks/helpers/objOf'

const { Rejected, Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// hasError :: Boolean -> Object -> Object
const hasError =
  setProp('hasError')

// buildError :: a -> String
const buildError =
  x => `${x}: is not valid`

// buildResult :: (String, Boolean) -> a -> Object
const buildResult = (key, isError) =>
  compose(hasError(isError), objOf(key))

// finalize :: Bifunctor m => m a b -> m Object Object
const finalize = bimap(
  compose(buildResult('error', true), buildError),
  buildResult('result', false)
)

finalize(Resolved('Good To Go'))
  .fork(log('rej'), log('res'))
//=> res: { result: "Good To Go", hasError: false }

finalize(Rejected(null))
  .fork(log('rej'), log('res'))
//=> rej: { error: "null is not valid", hasError: true }

ap

Async e (a -> b) ~> Async e a -> Async e b

Short for apply, ap is used to apply an Async instance containing a value to another Async instance that contains a function, resulting in new Async instance with the result. ap requires that it is called on an instance that is either Rejected or Resolved that wraps a curried polyadic function.

When either Async is Rejected, ap will return a Rejected instance, that wraps the value of the original Rejected instance. This can be used to safely combine multiple values under a given combination function. If any of the inputs result in a Rejected than they will never be applied to the function and will not result in undesired exceptions or results.

When forked, all Asyncs chained with multiple ap invocations will be executed concurrently.

import Async from 'crocks/Async'

import liftA2 from 'crocks/helpers/liftA2'

const { Rejected, Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// resolveAfter :: (Integer, a) -> Async e a
const resolveAfter = (delay, x) => Async(
  (rej, res) => setTimeout(() => res(x), delay)
)

// join :: String -> String -> String
const join =
  a => b => `${a} ${b}`

Async.of(join)
  .ap(Resolved('blip'))
  .ap(Resolved('blop'))
  .fork(log('rej'), log('res'))
//=> res: "blip blop"

Async.of(join)
  .ap(Resolved('blip'))
  .ap(Rejected('Not Good'))
  .fork(log('rej'), log('res'))
//=> rej: "Not Good"

Resolved('splish')
  .map(join)
  .ap(Resolved('splash'))
  .fork(log('rej'), log('res'))
//=> res: "splish splash"

// first :: Async e String
const first =
  resolveAfter(5000, 'first')

// second :: Async e String
const second =
  resolveAfter(5000, 'second')

// `ap` runs all Asyncs at the same time in parallel.
// This will finish running in about 5 seconds and
// not 10 seconds

liftA2(join, first, second)
  .fork(log('rej'), log('res'))
//=> res: "first second"

chain

Async e a ~> (a -> Async e b) -> Async e b

Combining a sequential series of transformations that capture disjunction can be accomplished with chain. chain expects a unary, Async returning function as its argument. When invoked on a Rejected instance, chain will not run the function, but will instead return another Rejected instance wrapping the original Rejected value. When called on a Resolved instance however, the inner value will be passed to provided function, returning the result as the new instance.

import Async from 'crocks/Async'

import chain from 'crocks/pointfree/chain'
import compose from 'crocks/helpers/compose'
import composeK from 'crocks/helpers/composeK'
import constant from 'crocks/combinators/constant'
import flip from 'crocks/combinators/flip'
import getProp from 'crocks/Maybe/getProp'
import ifElse from 'crocks/logic/ifElse'
import isString from 'crocks/predicates/isString'
import maybeToAsync from 'crocks/Async/maybeToAsync'

const { Rejected, Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// safe :: (b -> Boolean) -> b -> Async String a
const safe = pred =>
  ifElse(pred, Resolved, constant(Rejected('Not Safe')))

// test :: RegExp -> String -> Boolean
const test = regex => str =>
  regex.test(str)

// lookup :: String -> Async String String
const lookup = compose(
  maybeToAsync('Not Found'),
  flip(getProp, { 'file-a': 'file-b', 'file-b': 'file-c' })
)

// fake :: String -> Async String Object
const fake = compose(
  chain(lookup),
  chain(safe(test(/^file-(a|b|c)/))),
  safe(isString)
)

fake('file-a')
  .fork(log('rej'), log('res'))
//=> file-b

// getTwo :: a -> Async String String
const getTwo =
  composeK(fake, fake)

getTwo('file-a')
  .fork(log('rej'), log('res'))
//=> res: file-c

getTwo('file-b')
  .fork(log('rej'), log('res'))
//=> rej: "Not Found"

getTwo(76)
  .fork(log('rej'), log('res'))
//=> rej: "Not Safe"

coalesce

Async e a ~> ((e -> b), (a -> b))) -> Async e b

Used as a means to apply a computation to a Resolved instance and then map any Rejected value while transforming it to a Resolved to continue computation. coalesce on an Async can be used to model the all too familiar, and more imperative if/else flow in a more declarative manner.

The first function is used when invoked on a Rejected instance and will return a Resolved instance wrapping the result of the function. The second function is used when coalesce is invoked on a Resolved instance and is used to map the original value, returning a new Resolved instance wrapping the result of the second function.

import Async from 'crocks/Async'

import coalesce from 'crocks/pointfree/coalesce'

const { Rejected, Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// label :: String -> String -> String
const label =
  lbl => x => `${lbl} ${x}`

// resolve :: Async e String -> Async e String
const resolve =
  coalesce(label('Was'), label('Still'))

resolve(Resolved('Resolved'))
  .fork(log('rej'), log('res'))
//=> res: "Still Resolved"

resolve(Rejected('Rejected'))
  .fork(log('rej'), log('res'))
//=> res: "Was Rejected"

bichain

Async e a ~> ((e -> Async b c), (a -> Async b c)) -> Async b c

Combining a sequential series of transformations that capture disjunction can be accomplished with chain. Along the same lines, bichain allows you to do this from both Rejected and Resolved. bichain expects two unary, Async returning functions as its arguments. When invoked on a Rejected instance, bichain will use the left, or first, function that can return either a Rejected or Resolved instance. When called on a Resolved instance, it will behave exactly as chain would with the right, or second function.

import bichain from 'crocks/pointfree/bichain'

import Async from 'crocks/Async'

import equals from 'crocks/pointfree/equals'
import maybeToAsync from 'crocks/Async/maybeToAsync'
import propSatisfies from 'crocks/predicates/propSatisfies'
import safe from 'crocks/Maybe/safe'
import substitution from 'crocks/combinators/substitution'

const { Rejected, Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

const fork = m =>
  m.fork(log('rej'), log('res'))

fork(
  bichain(Resolved, Rejected, Resolved(42))
)
//=> rej: 42

fork(
  bichain(Resolved, Rejected, Rejected(42))
)
//=> res: 42

// fake401 :: Async Response a
const fake401 = Rejected({
  status: 'Unauthorized',
  statusCode: 401
})

// fake500 :: Async Response a
const fake500 = Rejected({
  status: 'Internal Server Error',
  statusCode: 500
})

// fake200 :: Async e Response
const fake200 = Resolved({
  status: 'OK',
  statusCode: 200
})

// allow401 :: Response -> Async e a
const allow401 = substitution(
  maybeToAsync,
  safe(propSatisfies('statusCode', equals(401)))
)

fork(bichain(allow401, Resolved, fake500))
//=> rej: { status: 'Internal Server Error', statusCode: 500 }

fork(bichain(allow401, Resolved, fake401))
//=> res: { status: 'Unauthorized', statusCode: 401 }

fork(bichain(allow401, Resolved, fake200))
//=> res: { status: 'OK', statusCode: 200 }

swap

Async e a ~> ((e -> b), (a -> c)) -> Async c b

Used to map the value of a Rejected into a Resolved or a Resolved to a Rejected, swap takes two functions as its arguments. The first function is used to map the expected Rejected value into a Resolved, while the second goes from Resolved to Rejected. If no mapping is required on either, then identity functions can be used in one or both arguments.

import Async from 'crocks/Async'

import compose from 'crocks/helpers/compose'
import identity from 'crocks/combinators/identity'
import swap from 'crocks/pointfree/swap'

const { Rejected, Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// length :: String -> Integer
const length =
  x => x.length

// repeat :: String -> Number -> String
const repeat =
  char => n => char.repeat(n)

// values :: Async String Number -> Async String Number
const values =
  swap(length, repeat('a'))

// valueIso :: Async String Number -> Async String Number
const valueIso =
  compose(values, values)

// types :: Async a b -> b a
const types =
  swap(identity, identity)

// typeIso :: Async a b -> b a
const typeIso =
  compose(types, types)

values(Resolved(5))
  .fork(log('rej'), log('res'))
//=> rej: "aaaaa"

values(Rejected('aaaaa'))
  .fork(log('rej'), log('res'))
//=> res: 5

valueIso(Resolved(5))
  .fork(log('rej'), log('res'))
//=> res: 5

valueIso(Rejected('aaaaa'))
  .fork(log('rej'), log('res'))
//=> rej: "aaaaa"

types(Resolved(5))
  .fork(log('rej'), log('res'))
//=> rej: 5

types(Rejected('aaaaa'))
  .fork(log('rej'), log('res'))
//=> res: "aaaaa"

typeIso(Resolved(5))
  .fork(log('rej'), log('res'))
//=> res: 5

typeIso(Rejected('aaaaa'))
  .fork(log('rej'), log('res'))
//=> rej: "aaaaa"

race

Async e a ~> Async e a -> Async e a

Used to provide the first settled result between two Asyncs. Just pass race another Async and it will return new Async, that when forked, will run both Asyncs in parallel, returning the first of the two to settle. The result can either be rejected or resolved, based on the instance of the first settled result.

import Async from '../crocks/src/Async'

const { resolveAfter, rejectAfter } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

resolveAfter(300, 'I win')
  .race(resolveAfter(400, 'I lose'))
  .fork(log('rejected'), log('resolved'))
//=> resolved: "I win"

rejectAfter(500, 'I lose')
  .race(rejectAfter(300, 'I win'))
  .fork(log('rejected'), log('resolved'))
//=> rejected: "I win"

resolveAfter(500, 'I lose')
  .race(rejectAfter(300, 'I win'))
  .fork(log('rejected'), log('resolved'))
//=> rejected: "I win"

fork

Async e a ~> ((e -> ()), (a -> ())) -> (() -> ())
Async e a ~> ((e -> ()), (a -> ()), (() -> ())) -> (() -> ())

The Async type is lazy and will not be executed until told to do so and fork is the primary method used for execution. fork implements two signatures depending on the need for clean up in the event of cancellation, but both return a function that can be used for cancellation of a given instance.

The first and more common signature takes two functions that will have their return values ignored. The first function will be run in the event of the Async instance settling on Rejected and will receive as its single argument the value or "cause" of rejection. The second function provided will be executed in the case of the instance settling on Resolved and will receive as its single argument the value the Async was resolved with.

The second signature is used when any cleanup needs to be performed after a given Async is canceled by having the function returned from fork called. The first two arguments to the signature are the same as the more common signature described above, but takes an addition function that can be used for "clean up" after cancellation. When all in-flight computations settle, the function provided will be silently executed.

import Async from 'crocks/Async'

import compose from 'crocks/helpers/compose'

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// inc :: Number -> Number
const inc =
  n => n + 1

// delay :: a -> Async e a
const delay = x => Async(
  (rej, res) => { setTimeout(() => res(x), 1000) }
).map(compose(inc, log('value')))

delay(0)
  .chain(delay)
  .chain(delay)
  .fork(log('rej'), log('res'))
//=> value: 0
//=> value: 1
//=> value: 2
//=> res: 3

const cancel =
  delay(0)
    .chain(delay)
    .chain(delay)
    .fork(log('rej'), log('res'))
//=> value: 0
//=> value: 1

setTimeout(cancel, 2200)

toPromise

Async e a ~> () -> Promise a e

While fork is the more common method for running an Async instance, there may come time where a Promise is needed at the edge of a given program or flow. When the need to integrate into an existing Promise chain arises, Async provides the toPromise method.

toPromise takes no arguments and when invoked will fork the instance internally and return a Promise that will be in-flight. This comes in handy for integration with other Promise based libraries that are utilized in a given application, program or flow.

import Async from 'crocks/Async'

const { Rejected, Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

Resolved('resolved')
  .toPromise()
  .then(log('res'))
  .catch(log('rej'))
//=> res: resolved

Rejected('rejected')
  .toPromise()
  .then(log('res'))
  .catch(log('rej'))
//=> rej: rejected

Pointfree Functions

race (pointfree)

crocks/Async/race

race :: Async e a -> Async e a -> Async e a

The race pointfree function accepts two Async instances and will return a new Async instance that is the result of applying the first argument to the race method on the second passed instance.

import race from 'crocks/Async/race'
import Async from 'crocks/Async'

const { resolveAfter, rejectAfter } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// timeout :: Async Error a -> Async Error a
const timeout =
  race(rejectAfter(300, new Error('Request has timed out')))

// fast :: Async e String
const fast =
  resolveAfter(150, 'All good')

// slow :: Async e Boolean
const slow =
  resolveAfter(900, true)

timeout(fast)
  .fork(log('rejected'), log('resolved'))
//=> resolved: "All good"

timeout(slow)
  .fork(log('rejected'), log('resolved'))
//=> rejected: "Error: Request has timed out"

Transformation Functions

asyncToPromise

crocks/Async/asyncToPromise

asyncToPromise :: Async e a -> Promise a e
asyncToPromise :: (a -> Async e b) -> a -> Promise b e

The asyncToPromise function takes an Async and when invoked will fork the instance internally and return a Promise that will be in-flight. This comes in handy for integration with other Promise based libraries that are utilized in a given application, program or flow through composition.

import Async from 'crocks/Async'
import race from 'crocks/Async/race'
import asyncToPromise from 'crocks/Async/asyncToPromise'

import ifElse from 'crocks/logic/ifElse'
import compose from 'crocks/helpers/compose'
import isPromise from 'crocks/pointfree/isPromise'

const { resolveAfter, rejectAfter } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// logIt :: Promise a e -> Promise a e
const logIt = p =>
  p.then(log('resolved'), log('rejected'))

const logResult =
  compose(logIt, ifElse(isPromise, x => x, asyncToPromise))

const failingPromise =
  new Promise((resolve, reject) => setTimeout(() => reject('Promise rejected!'), 300))

// timeout :: Async Error a -> Async Error a
const timeout =
  race(rejectAfter(300, new Error('Request has timed out')))

// fast :: Async e String
const fast =
  resolveAfter(150, 'All good')

// slow :: Async e Boolean
const slow =
  resolveAfter(900, true)

logResult(timeout(fast))
//=> resolved: "All good"

logResult(timeout(slow))
//=> rejected: "Error: Request has timed out"

logResult(failingPromise)
//=> rejected: "Promise rejected!"

eitherToAsync

crocks/Async/eitherToAsync

eitherToAsync :: Either b a -> Async b a
eitherToAsync :: (a -> Either c b) -> a -> Async c b

Used to transform a given Either instance to an Async instance or flatten an Async of Either into an Async when chained, eitherToAsync will turn a Right instance into a Resolved instance wrapping the original value contained in the original Right. If aLeft is provided, then eitherToAsync will return aRejected instance, wrapping the original Left value.

Like all crocks transformation functions, eitherToAsync has two possible signatures and will behave differently when passed an Either instance or a function that returns an instance of Either. When passed the instance, a transformed Async is returned. When passed an Either returning function, a function will be returned that takes a given value and returns an Async.

import Async from 'crocks/Async'
import Either from 'crocks/Either'

import eitherToAsync from 'crocks/Async/eitherToAsync'

import and from 'crocks/logic/and'
import ifElse from 'crocks/logic/ifElse'
import isNumber from 'crocks/predicates/isNumber'

const { Resolved } = Async
const { Left, Right } = Either

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// gte :: Number -> Number -> Boolean
const gte =
  x => y => y >= x

// isLarge :: a -> Boolean
const isLarge =
  and(isNumber, gte(10))

// isValid :: a -> Either String a
const isValid = ifElse(
  isLarge,
  Right,
  x => Left(`${x} is not valid`)
)

eitherToAsync(Right('Correct'))
  .fork(log('rej'), log('res'))
//=> res: "Correct"

eitherToAsync(Left('Not Good'))
  .fork(log('rej'), log('res'))
//=> rej: "Not Good"

Resolved(54)
  .chain(eitherToAsync(isValid))
  .fork(log('rej'), log('res'))
//=> res: 54

Resolved(4)
  .chain(eitherToAsync(isValid))
  .fork(log('rej'), log('res'))
//=> rej: "4 is not valid"

Resolved('Bubble')
  .chain(eitherToAsync(isValid))
  .fork(log('rej'), log('res'))
//=> rej: "Bubble is not valid"

Resolved(Left('Alone'))
  .chain(eitherToAsync)
  .fork(log('rej'), log('res'))
//=> rej: "Alone"

Resolved(Right('Away'))
  .chain(eitherToAsync)
  .fork(log('rej'), log('res'))
//=> res: "Away"

firstToAsync

crocks/Async/firstToAsync

firstToAsync :: e -> First a -> Async e a
firstToAsync :: e -> (a -> First b) -> a -> Async e b

Used to transform a given First instance to an Async instance or flatten an Async of First into an Async when chained, firstToAsync will turn a non-empty First instance into a Resolvedinstance wrapping the original value contained in the original non-empty.

The First datatype is based on a Maybe and as such its left or empty value is fixed to a () type. As a means to allow for convenient transformation, firstToAsync takes a default Rejected value as the first argument. This value will be wrapped in a resulting Rejected instance in the case of empty.

Like all crocks transformation functions, firstToAsync has two possible signatures and will behave differently when passed either a First instance or a function that returns an instance of First. When passed the instance, a transformed Async is returned. When passed a First returning function, a function will be returned that takes a given value and returns an Async.

import Async from 'crocks/Async'
import First from 'crocks/First'

import firstToAsync from 'crocks/Async/firstToAsync'

import Pred from 'crocks/Pred'
import isString from 'crocks/predicates/isString'
import mconcatMap from 'crocks/helpers/mconcatMap'
import safe from 'crocks/Maybe/safe'

const { Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// length :: String -> Number
const length =
  x => x.length

// gte :: Number -> Number -> Boolean
const gte =
  x => y => y >= x

// isValid :: Pred String
const isValid =
  Pred(isString)
    .concat(Pred(gte(4)).contramap(length))

// firstValid :: [ String ] -> First String
const firstValid =
  mconcatMap(First, safe(isValid))

// findFirstValid :: [ String ] -> Async String String
const findFirstValid =
  firstToAsync('Nothing Found', firstValid)

firstToAsync('Error', First(true))
  .fork(log('rej'), log('res'))
//=> res: true

firstToAsync('Error', First.empty())
  .fork(log('rej'), log('res'))
//=> rej: "Error"

Resolved([ 'cat', 'rhino', 'unicorn' ])
  .chain(findFirstValid)
  .fork(log('rej'), log('res'))
//=> res: "rhino"

Resolved([ 1, 2, 3 ])
  .chain(findFirstValid)
  .fork(log('rej'), log('res'))
//=> rej: "Nothing Found"

Resolved([ 'cat', 'bat', 'imp' ])
  .chain(findFirstValid)
  .fork(log('rej'), log('res'))
//=> rej: "Nothing Found"

Resolved(First.empty())
  .chain(firstToAsync('Left'))
  .fork(log('rej'), log('res'))
//=> rej: "Left"

Resolved(First(42))
  .chain(firstToAsync('Left'))
  .fork(log('rej'), log('res'))
//=> res: 42

lastToAsync

crocks/Async/lastToAsync

lastToAsync :: e -> Last a -> Async e a
lastToAsync :: e -> (a -> Last b) -> a -> Async e b

Used to transform a given Last instance to an Async instance or flatten an Async of Last into an Async when chained, lastToAsync will turn a non-empty Last instance into a Resolvedinstance wrapping the original value contained in the original non-empty.

The Last datatype is based on a Maybe and as such its left or empty value is fixed to a () type. As a means to allow for convenient transformation, lastToAsync takes a default Rejected value as the first argument. This value will be wrapped in a resulting Rejected instance, in the case of empty.

Like all crocks transformation functions, lastToAsync has two possible signatures and will behave differently when passed either a Last instance or a function that returns an instance of Last. When passed the instance, a transformed Async is returned. When passed a Last returning function, a function will be returned that takes a given value and returns an Async.

import Async from 'crocks/Async'
import Last from 'crocks/Last'

import lastToAsync from 'crocks/Async/lastToAsync'

import Pred from 'crocks/Pred'
import isString from 'crocks/predicates/isString'
import mconcatMap from 'crocks/helpers/mconcatMap'
import safe from 'crocks/Maybe/safe'

const { Resolved } = Async

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// length :: String -> Number
const length =
  x => x.length

// gte :: Number -> Number -> Boolean
const gte =
  x => y => y >= x

// isValid :: Pred String
const isValid =
  Pred(isString)
    .concat(Pred(gte(4)).contramap(length))

// lastValid :: [ String ] -> Last String
const lastValid =
  mconcatMap(Last, safe(isValid))

// findLastValid :: [ String ] -> Async String String
const findLastValid =
  lastToAsync('Nothing Found', lastValid)

lastToAsync('Error', Last(true))
  .fork(log('rej'), log('res'))
//=> res: true

lastToAsync('Error', Last.empty())
  .fork(log('rej'), log('res'))
//=> rej: "Error"

Resolved([ 'unicorn', 'rhino', 'cat' ])
  .chain(findLastValid)
  .fork(log('rej'), log('res'))
//=> res: "rhino"

Resolved([ 1, 2, 3 ])
  .chain(findLastValid)
  .fork(log('rej'), log('res'))
//=> rej: "Nothing Found"

Resolved([ 'cat', 'bat', 'imp' ])
  .chain(findLastValid)
  .fork(log('rej'), log('res'))
//=> rej: "Nothing Found"

Resolved(Last.empty())
  .chain(lastToAsync('Left'))
  .fork(log('rej'), log('res'))
//=> rej: "Left"

Resolved(Last('too know!'))
  .chain(lastToAsync('Left'))
  .fork(log('rej'), log('res'))
//=> res: "too know!"

maybeToAsync

crocks/Async/maybeToAsync

maybeToAsync :: e -> Maybe a -> Async e a
maybeToAsync :: e -> (a -> Maybe b) -> a -> Async e b

Used to transform a given Maybe instance to an Async instance or flatten an Async of Maybe into an Async when chained, maybeToAsync will turn a Just instance into a Resolved instance wrapping the original value contained in the original Just.

A Nothing instance is fixed to a () type and as such can only ever contain a value of undefined. As a means to allow for convenient transformation, maybeToAsync takes a default Rejected value as the first argument. This value will be wrapped in a resulting Rejected instance, in the case of Nothing.

Like all crocks transformation functions, maybeToAsync has two possible signatures and will behave differently when passed either a Maybe instance or a function that returns an instance of Maybe. When passed the instance, a transformed Async is returned. When passed a Maybe returning function, a function will be returned that takes a given value and returns an Async.

import Async from 'crocks/Async'
import Maybe from 'crocks/Maybe'

import maybeToAsync from 'crocks/Async/maybeToAsync'

import and from 'crocks/logic/and'
import isEmpty from 'crocks/predicates/isEmpty'
import isArray from 'crocks/predicates/isArray'
import not from 'crocks/logic/not'
import safe from 'crocks/Maybe/safe'

const { Resolved } = Async
const { Nothing, Just } = Maybe

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// isValid :: a -> Maybe [ b ]
const isValid = safe(
  and(isArray, not(isEmpty))
)

maybeToAsync(false, Just(true))
  .fork(log('rej'), log('res'))
//=> res: true

maybeToAsync('Bad', Nothing())
  .fork(log('rej'), log('res'))
//=> rej: "Bad"

Resolved([ 'a', 'b', 'c' ])
  .chain(maybeToAsync('Invalid', isValid))
  .fork(log('rej'), log('res'))
//=> res: [ 'a', 'b', 'c' ]

Resolved([])
  .chain(maybeToAsync('Invalid', isValid))
  .fork(log('rej'), log('res'))
//=> rej: "Invalid"

Resolved('')
  .chain(maybeToAsync('Invalid', isValid))
  .fork(log('rej'), log('res'))
//=> rej: "Invalid"

Resolved(Nothing())
  .chain(maybeToAsync('Left'))
  .fork(log('rej'), log('res'))
//=> rej: "Left"

Resolved(Just('the 2 of us'))
  .chain(maybeToAsync('Left'))
  .fork(log('rej'), log('res'))
//=> res: "the 2 of us"

resultToAsync

crocks/Async/resultToAsync

resultToAsync :: Result b a -> Async b a
resultToAsync :: (a -> Result c b) -> a -> Async c b

Used to transform a given Result instance to an Async instance or flatten an Async of Result into an Async when chained, resultToAsync will turn an Ok instance into a Resolved instance wrapping the original value contained in the original Ok. If an Err is provided, then resultToAsync will return a Rejected instance, wrapping the original Err value.

Like all crocks transformation functions, resultToAsync has two possible signatures and will behave differently when passed either a Result instance or a function that returns an instance of Result. When passed the instance, a transformed Async is returned. When passed a Result returning function, a function will be returned that takes a given value and returns an Async.

import Async from 'crocks/Async'

import Result from 'crocks/Result'

import resultToAsync from 'crocks/Async/resultToAsync'

import identity from 'crocks/combinators/identity'
import isNumber from 'crocks/predicates/isNumber'
import tryCatch from 'crocks/Result/tryCatch'

const { Resolved } = Async

const { Err, Ok }  = Result

// log :: String -> a -> a
const log = label => x =>
  (console.log(`${label}:`, x), x)

// notNumber :: a -> Number
function notNumber(x) {
  if (!isNumber(x)) {
    throw new TypeError('Must be a Number')
  }
  return x
}

// safeFail :: a -> Result TypeError Number
const safeFail =
  tryCatch(notNumber)

resultToAsync(Ok(99))
  .fork(log('rej'), log('res'))
//=> res: 99

resultToAsync(Err('Not Good'))
  .fork(log('rej'), log('res'))
//=> rej: "Not Good"

Resolved(103)
  .chain(resultToAsync(safeFail))
  .bimap(x => x.message, identity)
  .fork(log('rej'), log('res'))
//=> res: 103

Resolved('103')
  .chain(resultToAsync(safeFail))
  .bimap(x => x.message, identity)
  .fork(log('rej'), log('res'))
//=> rej: "Must be a Number"

Resolved(Err('Invalid entry'))
  .chain(resultToAsync)
  .fork(log('rej'), log('res'))
//=> rej: "Invalid entry"

Resolved(Ok('Success!'))
  .chain(resultToAsync)
  .fork(log('rej'), log('res'))
//=> res: "Success!"

Contribute on Github! Edit this section.