// @flow /* Porting of purescript-aff https://github.com/slamdata/purescript-aff */ import { HKT } from './HKT' import type { HKT2 } from './HKT' import type { Monad } from './Monad' import type { Eff } from './Eff' import { EXCEPTION, throwException } from './Exception' import { constant } from './Fun' import * as eff from './Eff' class IsAff {} export type ErrorHandler = (e: Error) => Eff; export type SuccessHandler = (a: A) => Eff; // An asynchronous computation with effects `e`. The computation either // errors or produces a value of type `a`. export type AffV = (s: (a: A) => void, e: (e: Error) => void) => Canceler; export type Aff = HKT2; // A pure asynchronous computation, having no effects other than // asynchronous computation. export type PureAff = Aff<{}, A>; // A canceler is an asynchronous function that can be used to attempt the // cancelation of a computation. Returns a boolean flag indicating whether // or not the cancellation was successful. Many computations may be composite, // in such cases the flag indicates whether any part of the computation was // successfully canceled. The flag should not be used for communication. export type Canceler = (e: Error) => Aff; export function prj(aff: Aff): AffV { return ((aff: any): AffV) } export function inj(affv: AffV): Aff { return ((affv: any): Aff) } // Runs the asynchronous computation. You must supply an error callback and a // success callback. export function runAff( error: (e: Error) => Eff, // <= do not use ErrorHandler type here success: (a: A) => Eff, // <= do not use SuccessHandler type here aff: Aff): Eff> { return eff.inj(() => prj(aff)(a => { eff.runEff(success(a)) }, e => { eff.runEff(error(e)) })) } // A constant canceler that always returns false. export function nonCanceler(): Canceler { return constant(of(false)) } // A constant canceller that always returns true. export function alwaysCanceler(): Canceler { return constant(of(true)) } // Converts the asynchronous computation into a synchronous one. All values // are ignored, and if the computation produces an error, it is thrown. // // Catching exceptions by using `catchException` with the resulting Eff // computation is not recommended, as exceptions may end up being thrown // asynchronously, in which case they cannot be caught. // // If you do need to handle exceptions, you can use `runAff` instead export function launchAff (aff: Aff): Eff<{ err: EXCEPTION }, Canceler> { return runAff(throwException, constant(eff.of(undefined)), aff) } export function map(f: (a: A) => B, fa: Aff): Aff { return inj((success, error) => { return prj(fa)(a => success(f(a)), error) }) } export function ap(fab: Aff B>, fa: Aff): Aff { return chain(f => map(f, fa), fab) // <= derived } export function of(a: A): Aff { return inj((success) => { success(a) return nonCanceler() }) } export function chain(f: (a: A) => Aff, fa: Aff): Aff { return inj((success, error) => { let isCanceled = false let requestCancel = false let onCanceler = (canceler) => {} // eslint-disable-line no-unused-vars let canceler2: ?Canceler = null const canceler1: Canceler = prj(fa)(v => { if (requestCancel) { isCanceled = true } else { canceler2 = prj(f(v))(success, error) onCanceler(canceler2) } }, error) return (e: Error): Aff => { return inj((s, f) => { requestCancel = true if (canceler2 != null) { return prj(canceler2(e))(s, f) } else { return prj(canceler1(e))(bool => { if (bool || isCanceled) { s(true) } else { onCanceler = canceler => { prj(canceler(e))(s, f) } } }, f) } }) } }) } if (false) { // eslint-disable-line ({ map, ap, of, chain }: Monad>) }