Small future implemenatation
yarn add @typed/future
# or
npm install --save @typed/futureAll functions are curried!
Fork function signature used by Future.
export type Fork<A, B> = (reject: (value: A) => void, resolve: (value: B) => void) => Disposable
Asynchronous data-structure similar to a Promise, but lazy.
export interface Future<A, B> {
readonly fork: Fork<A, B>
}
Creates a Future given a Fork function.
Note: It is the responsibility of the caller to Future.create that
neither of the 2 supplied functions will be invoked until the caller has been
able to return it's Disposable. If you looking to use Future for something
naturally synchronous, @typed/either
is likely a better choice for your use case.
See the code
export function create<A, B>(fork: Fork<A, B>): Future<A, B> {
return {
fork: (reject: (value: A) => void, resolve: (value: B) => void): Disposable => {
let settled = false
function isUnsettled(): boolean {
if (settled) return false
settled = true
return true
}
const disposable = disposeOnce(
fork(value => isUnsettled() && reject(value), value => isUnsettled() && resolve(value))
)
const dispose = () => disposable.dispose()
return { dispose }
},
}
}
Creates a Future which will always fork to the right with the given value.
See the code
export function of<A, B = any>(value: A): Future<B, A> {
return create((_, resolve) => defer(resolve, value))
}
Creates a Future which will always fork to the left with the given value.
See the code
export function reject<A, B = any>(value: A): Future<A, B> {
return create(reject => defer(reject, value))
}
}
function defer<A>(f: (value: A) => void, value: A): Disposable {
const id = setTimeout(f, 0, value)
const dispose = () => clearTimeout(id)
return { dispose }
}
Creates a Future that concurrently forks all of the given Futures resolving
with an Array of their values, or rejecting if any of the futures reject. Conceptually
the same as to Promise.all.
See the code
export function all<A, B>(futures: ArrayLike<Future<A, B>>): Future<A, ReadonlyArray<B>> {
const { length: futureCount } = futures
const values: Array<B> = Array(futureCount)
const remaining: Remaining = { count: futureCount }
return Future.create<A, ReadonlyArray<B>>((reject, resolve) =>
disposeAll(reduce(forkFuture<A, B>(reject, resolve, values, remaining), [], futures))
)
}
type Remaining = { count: number }
function forkFuture<A, B>(
reject: (value: A) => void,
resolve: (value: ReadonlyArray<B>) => void,
values: Array<B>,
remaining: Remaining
) {
return (disposables: Array<Disposable>, future: Future<A, B>, index: number): Array<Disposable> =>
append(
fork(
reject,
value => {
values[index] = value
remaining.count--
if (remaining.count === 0) resolve(values)
},
future
),
disposables
)
}
Applies the function resolved from a Future to the value resolved from a
second Future.
See the code
export const ap: FutureAp = curry2(__ap)
function __ap<A, B, C>(fn: Future<A, (value: B) => C>, value: Future<A, B>): Future<A, C> {
return chain(f => map(f, value), fn)
}
export type FutureAp = {
<A, B, C>(fn: Future<A, (value: B) => C>, value: Future<A, B>): Future<A, C>
<A, B, C>(fn: Future<A, (value: B) => C>): (value: Future<A, B>) => Future<A, C>
}
Returns a Future that is the result of calling f with the resolved
value of another future. Similar to Promise.then.
See the code
export const chain: FutureChain = curry2(__chain)
function __chain<A, B, C>(f: (value: B) => Future<A, C>, future: Future<A, B>): Future<A, C> {
return Future.create((reject, resolve) =>
future.fork(reject, value => f(value).fork(reject, resolve))
)
}
export type FutureChain = {
<A, B, C>(f: (value: B) => Future<A, C>, future: Future<A, B>): Future<A, C>
<A, B, C>(f: (value: B) => Future<A, C>): (future: Future<A, B>) => Future<A, C>
}
Returns a Future that is the result of calling f with the rejected
value of another future. Similar to Promise.catch.
See the code
export const chainLeft: FutureChainLeft = curry2(__chainLeft)
function __chainLeft<A, B, C>(f: (value: A) => Future<C, B>, future: Future<A, B>): Future<C, B> {
return Future.create((reject, resolve) =>
future.fork(value => f(value).fork(reject, resolve), resolve)
)
}
export type FutureChainLeft = {
<A, B, C>(f: (value: A) => Future<C, B>, future: Future<A, B>): Future<C, B>
<A, B, C>(f: (value: A) => Future<C, B>): (future: Future<A, B>) => Future<C, B>
}
Activates a future (side-effectful).
See the code
export const fork: ForkFn = curry3(forkFuture)
function forkFuture<A, B>(
left: (value: A) => any,
right: (value: B) => any,
future: Future<A, B>
): Disposable {
return future.fork(left, right)
}
export interface ForkFn {
<A, B>(left: (value: A) => any, right: (value: B) => any, future: Future<A, B>): Disposable
<A, B>(left: (value: A) => any): (right: (value: B) => any, future: Future<A, B>) => Disposable
<A, B>(left: (value: A) => any, right: (value: B) => any): (future: Future<A, B>) => Disposable
<A, B>(left: (value: A) => any): (
right: (value: B) => any
) => (future: Future<A, B>) => Disposable
}
Maps the value of a Future. Similar to Promise.then.
See the code
export const map: FutureMap = curry2(__map)
function __map<A, B, C>(f: (value: B) => C, future: Future<A, B>): Future<A, C> {
return chain(b => Future.of(f(b)), future)
}
export type FutureMap = {
<A, B, C>(f: (value: B) => C, future: Future<A, B>): Future<A, C>
<A, B, C>(f: (value: B) => C): (future: Future<A, B>) => Future<A, C>
}
Returns a Future that is the result of calling f with the rejected
value of another future. Similar to Promise.catch.
See the code
export const mapLeft: FutureMapLeft = curry2(__mapLeft)
function __mapLeft<A, B, C>(f: (value: A) => C, future: Future<A, B>): Future<C, B> {
return chainLeft(value => Future.reject(f(value)), future)
}
export type FutureMapLeft = {
<A, B, C>(f: (value: A) => C, future: Future<A, B>): Future<C, B>
<A, B, C>(f: (value: A) => C): (future: Future<A, B>) => Future<C, B>
}
Creates a Future that will lazily fork each Future one after another.
Similar to all except that concurrency is always 1.
See the code
export function sequence<A, B>(futures: ArrayLike<Future<A, B>>): Future<A, ReadonlyArray<B>> {
let seed = Future.of<Array<B>, A>([])
for (let i = 0; i < futures.length; ++i) {
const future = futures[i]
seed = chain(values => map(value => values.concat(value), future), seed)
}
return seed
}
Forks a Future into a Promise
See the code
export function toPromise<A>(future: Future<any, PromiseLike<A>>): Promise<A>
export function toPromise<A>(future: Future<any, A>): Promise<A>
export function toPromise<A>(future: Future<any, A>): Promise<A> {
return new Promise<A>((resolve, reject) => future.fork(reject, resolve))
}