Reference to the current ICancelable available for subsequent data transformations.
Protected, because it shouldn't be public API, being meant for
Future
implementations.
Reference to the current Scheduler available for subsequent
data transformations. Can be set in Future
's constructors, or by
transforming the source by withScheduler.
Protected, because it shouldn't be public API, being meant for
Future
implementations.
Exposes underlying errors by lifting both successful and failed
results into an Either
value.
Given that errors are short-circuiting the processing of flatMap
chains, this method is useful for exposing errors such that you can
flatMap
over them.
const f: Future<number> = Future.raise(new DummyError)
// Yields a successful Left(DummyError) on completion
const fe: Future<Either<Throwable, number>> = f.attempt()
// Yields a Right(1) on completion
const fr: Future<Either<Throwable, number>> = Future.pure(1).attempt()
In case this Future
isn't complete, then send it a cancel signal.
Depending on the computation that will complete this future, its execution might be interrupted.
Execution has the same properties of ICancelable, being idempotent (calling it multiple times has the same effect as calling it once).
In order to create a cancelable Future
, use Future.create.
Delays signaling the result of this Future
by the specified duration.
It works for successful results:
const fa = Future.of(() => "Alex")
// Delays the signaling by 1 second
fa.delayResult(1000).flatMap
And for failures as well:
Future.raise(new TimeoutError()).delayResult(1000)
is the duration to wait before signaling the final result
Chains asynchronous operations.
Creates a new future by applying a function to the successful result of the source and returns the result of the function as the new future. If this future is completed with an exception then the new future will also contain this exception.
This operation is the monadic bind (e.g. Monad.flatMap
).
const fa = Future.of(() => 3)
const fb = Future.of(() => 5)
// Yields 3 + 5
fa.flatMap(a => fb.map(b => a + b))
Asynchronously processes the value in the future once the value becomes available.
WARNING: Will not be called if this future is never completed or if it is completed with a failure.
the function which will be executed if this Future
completes with a result
Given a mapping function, transforms the successful result of the source.
If the source is completed with an exception, then the new future will also be completed in an error.
This operation is the functor map (e.g. Functor.map
).
const f = Future.of(() => "The future")
const g = f.map(x => x + " is now!")
Given a callback, calls it with this Future
's result when that result
is ready.
The execution of this callback is always trampolined (for already completed futures), or asynchronous, which means that modeling loops based on it is memory safe.
Future.of(() => "John").complete(r => {
r.fold(
error => console.info("Error: " + error),
success => console.info("Hello, " + John)
)
})
Creates a new future that will handle any matching throwable that this future might contain by assigning it a value.
This operation is the equivalent of map for handling errors. Also see transform, which can handle both successful results and failures.
const f = Future.of<number>(() => { throw new DummyError() })
f.recover(e => {
if (e instanceof DummyError) return 10
// Don't re-throw exceptions like this, use `recoverWith` instead!
throw e
})
Creates a new future that will handle any matching throwable that this future might contain by assigning it a value of another future.
This operation is the equivalent of flatMap for handling errors. Also see transformWith, which can handle both successful results and failures.
const f = Future.of<number>(() => { throw new DummyError() })
f.recoverWith(e => e instanceof DummyError
? Future.pure(10) // Fallback
: Future.raise(e) // Re-throw
)
JavaScript Thenable
implementation, needed in order to await
Future
values in async
functions.
Returns a future that mirrors the source in case the result of the source
is signaled within the required after
duration, otherwise it
fails with a TimeoutError
, cancelling the source.
const fa = Future.of(() => 1).delayResult(10000)
// Will fail with a TimeoutError
fa.timeout(1000)
is the duration to wait until it triggers the timeout error
Returns a future that mirrors the source in case the result of the source
is signaled within the required after
duration, otherwise it
triggers the execution of the given fallback
after the duration has
passed, cancelling the source.
This is literally the implementation of Future.timeout:
const fa = Future.of(() => 1).delayResult(10000)
fa.timeoutTo(1000, () => Future.raise(new TimeoutError()))
is the duration to wait until it triggers the fallback
is a thunk generating a fallback Future
to timeout to
Transforms this Future<A>
reference into a standard JavaScript Promise<A>
reference.
Normally a Future
is "thenable", so JavaScript should have no problem
working with it, however in certain contexts this conversion is useful for
working with type definitions that don't recognize the structural typing
defined by the Promises/A+ specification.
Transforms the sources, regardless if the result is a failure or a success.
This function is a combination of map and recover, being the (type safe) alternative to JavaScript's then from the Promises/A+ specification.
Example:
import { Left, Right } from "funfix"
// Expose errors by lifting them to an Either<Error, A>
future.transform<Either<Throwable, A>>(Left, Right)
Also see transformWith.
is the function that's going to get executed in case the source signals a failure
is the function that's going to get executed in case the source signals a successful result
Transforms the source, regardless if the result is a failure or a success.
This function is a combination of flatMap and recoverWith, being the (type safe) alternative to JavaScript's then from the Promises/A+ specification.
NOTE: in Funfix these fold-like methods, by convention, take as the
first parameter the function that transforms the failure (the left),
whereas the second parameter is the function that transforms the
successful result (the right). Think of Either<Error, A>
.
const randomInt = (max: number) =>
Future.of(() => {
const n = Math.random() * max
n & n
})
const randomEvenInt = (max: number) =>
randomInt(max).transformWith(
err => Future.pure(9),
value => (
// retry until we have an even value
value % 2 == 0 ? Future.pure(value) : randomEvenInt()
)
)
Also see transform.
is the function that's going to get executed in case the source signals a failure
is the function that's going to get executed in case the source signals a successful result
Extracts the completed value for this Future
, returning Some(result)
if this Future
is already complete or None
in case the Future
wasn't
completed yet.
const f1 = Future.of(() => 1)
// Given the async execution of `Future.of`, the immediate invocations of
// `value()` will yield `None`, but after complete it will yield
// `Some(Success(1))`
f1.value()
const f2 = Future.raise(new DummyError())
// Immediately yields Some(Failure(DummyError))
f2.value()
Sets the Scheduler reference that's going to get used for subsequent data transformations.
Future
references have a Scheduler reference attached at build
time, that's going to get used for data transformations. This method
returns a new Future
reference that's going to mirror the source,
but that's going to use the given Scheduler
for subsequent operations
like map
, flatMap
, transformWith
, etc.
const ec1 = new GlobalScheduler(true)
// The default Scheduler is global (that second parameter is optiona)
const f1 = Future.create(f, ec1)
// The `f1` future is going to get executed by `ec1`, however
// this subsequent `flatMap` is getting evaluated by `ec2`
const ec2 = new GlobalScheduler(false)
const f2 = f1.withScheduler(ec2).flatMap(x => Future.pure(x * 2))
When no Scheduler
is specified, the default is assumed to be
Scheduler.global.
is the scheduler that's going to get used asynchronous execution of subsequent operations
Given a side-effectful function that triggers an asynchronous computation,
execute it and return a Future
reference.
The given register
function will be invoked immediately to "schedule"
the asynchronous callback, where the callback is the parameter injected in
that function.
The register
function can optionally return a ICancelable
reference that can get used to cancel the running asynchronous
computation.
Example:
import { Scheduler, Future, Try, Duration, Cancelable } from "funfix"
const delay = <A>(d: Duration, f: () => A, ec: Scheduler = Scheduler.global.get()) =>
Future.create<A>(
cb => {
const task = ec.scheduleOnce(d, () => cb(Try.of(f)))
return Cancelable.of(() => {
console.warn("Delayed task was cancelled")
task.cancel()
})
},
ec
)
Note that by not returning a cancelable, the returned Future
reference
will NOT BE cancelable.
// This future is not cancelable, because we are not
// returning a cancelable reference
Future.create<number>(cb => {
setTimeout(1000, () => cb(Success(10)))
})
is the side-effectful function that will get invoked
to build our Future
, receiving a callback that's supposed to
get invoked (only once) when the asynchronous computation completes,
and that can optionally return a cancelable reference that can
get used to cancel the running computation
is an optional Scheduler reference that will get used for scheduling the actual async execution; if one isn't provided then Scheduler.global gets used, which also allows for local overrides, being a DynamicRef
Returns a Future
that will complete after the given delay
.
This can be used to do delayed execution. For example:
Future.delayedTick(1000).flatMap(_ =>
Future.of(() => console.info("Hello!"))
)
is the duration to wait before signaling the tick
is the scheduler that will actually schedule the tick's execution
Creates a race condition between multiple futures, returning the result of the first one that completes, cancelling the rest.
const failure = Future.raise(new TimeoutError()).delayResult(2000)
// Will yield 1
const fa1 = Future.of(() => 1).delayResult(1000)
Future.firstCompletedOf([fa1, failure])
// Will yield a TimeoutError
const fa2 = Future.of(() => 1).delayResult(10000)
Future.firstCompletedOf([fa2, failure])
is the list of futures for which the race is started
is the scheduler doing the needed scheduling and error reporting
a future that will complete with the result of the first future form the list to complete, the rest being cancelled
Transforms any Promise
-like data type into a Future
.
const p: Promise<number> = Promise.resolve(10)
const f: Future<number> = Future.fromPromise(p)
is the promise reference that we want to convert into a Future
is an optional Scheduler reference that will get used for scheduling the actual async execution; if one isn't provided then Scheduler.global gets used, which also allows for local overrides, being a DynamicRef
Builds an already complete Future
from a Try
value.
import { Success, Failure, Future } from "funfix"
// Already completed with 1
const f1 = Future.fromTry(Success(1))
// Already completed in error
const f2 = Future.fromTry(Failure("err"))
is the Try
value to stream in onComplete
listeners
is an optional Scheduler reference that will get used for scheduling the actual async execution; if one isn't provided then Scheduler.global gets used, which also allows for local overrides, being a DynamicRef
Maps 2 Future
values by the mapping function, returning a new
Future
reference that completes with the result of mapping that
function to the successful values of the futures, or in failure in
case either of them fails.
This is a specialized Future.sequence operation and as such on cancellation or failure all future values get cancelled.
const fa1 = Future.of(() => 1)
const fa2 = Future.of(() => 2)
// Yields Success(3)
Future.map2(fa1, fa2, (a, b) => a + b)
// Yields Failure, because the second arg is a Failure
Future.map2(fa1, Future.raise("error"),
(a, b) => a + b
)
This operation is the Applicative.map2
.
Maps 3 Future
values by the mapping function, returning a new
Future
reference that completes with the result of mapping that
function to the successful values of the futures, or in failure in
case either of them fails.
This is a specialized Future.sequence operation and as such on cancellation or failure all future values get cancelled.
const fa1 = Future.of(() => 1)
const fa2 = Future.of(() => 2)
const fa3 = Future.of(() => 3)
// Yields Success(6)
Future.map3(fa1, fa2, fa3, (a, b, c) => a + b + c)
// Yields Failure, because the second arg is a Failure
Future.map3(
fa1, fa2, Future.raise("error"),
(a, b, c) => a + b + c
)
This operation is the Applicative.map3
.
Maps 4 Future
values by the mapping function, returning a new
Future
reference that completes with the result of mapping that
function to the successful values of the futures, or in failure in
case either of them fails.
This is a specialized Future.sequence operation and as such on cancellation or failure all future values get cancelled.
const fa1 = Future.of(() => 1)
const fa2 = Future.of(() => 2)
const fa3 = Future.of(() => 3)
const fa4 = Future.of(() => 4)
// Yields Success(10)
Future.map4(fa1, fa2, fa3, fa4, (a, b, c, d) => a + b + c + d)
// Yields Failure, because the second arg is a Failure
Future.map4(
fa1, fa2, fa3, Future.raise("error"),
(a, b, c, d) => a + b + c + d
)
This operation is the Applicative.map4
.
Maps 5 Future
values by the mapping function, returning a new
Future
reference that completes with the result of mapping that
function to the successful values of the futures, or in failure in
case either of them fails.
This is a specialized Future.sequence operation and as such on cancellation or failure all future values get cancelled.
const fa1 = Future.of(() => 1)
const fa2 = Future.of(() => 2)
const fa3 = Future.of(() => 3)
const fa4 = Future.of(() => 4)
const fa5 = Future.of(() => 5)
// Yields Success(15)
Future.map5(fa1, fa2, fa3, fa4, fa5,
(a, b, c, d, e) => a + b + c + d + e
)
// Yields Failure, because the second arg is a Failure
Future.map5(
fa1, fa2, fa3, fa4, Future.raise("error"),
(a, b, c, d, e) => a + b + c + d + e
)
This operation is the Applicative.map5
.
Maps 6 Future
values by the mapping function, returning a new
Future
reference that completes with the result of mapping that
function to the successful values of the futures, or in failure in
case either of them fails.
This is a specialized Future.sequence operation and as such on cancellation or failure all future values get cancelled.
const fa1 = Future.of(() => 1)
const fa2 = Future.of(() => 2)
const fa3 = Future.of(() => 3)
const fa4 = Future.of(() => 4)
const fa5 = Future.of(() => 5)
const fa6 = Future.of(() => 6)
// Yields Success(21)
Future.map6(
fa1, fa2, fa3, fa4, fa5, fa6,
(a, b, c, d, e, f) => a + b + c + d + e + f
)
// Yields Failure, because the second arg is a Failure
Future.map6(
fa1, fa2, fa3, fa4, fa5, Future.raise("error"),
(a, b, c, d, e, f) => a + b + c + d + e + f
)
This operation is the Applicative.map6
.
Given a function that executes immediately, executes it asynchronously
and returns a Future
that will complete when the result is ready.
const sum = (x: number, y: number) =>
Future.of(() => x + y)
is the function to execute asynchronously
is an optional Scheduler reference that will get used for scheduling the actual async execution; if one isn't provided then Scheduler.global gets used, which also allows for local overrides, being a DynamicRef
Lifts a pure value into the Future
context, returning a Future
reference that's already complete with the given value.
This is the equivalent of Promise.resolve(a)
.
const f: Future<number> = Future.pure(10)
// Prints Success(10)
f.onComplete(r => console.info(r))
is the value to lift in the Future
context and that will
get signaled in onComplete
callbacks
is an optional Scheduler reference that will get used for scheduling the actual async execution; if one isn't provided then Scheduler.global gets used, which also allows for local overrides, being a DynamicRef
Lifts an error in the Future
context, returning a Future
reference
that's already failed with the given error.
This is the equivalent of Promise.reject
.
const f: Future<number> = Future.raise("Oops!")
// Prints Failure("Oops!")
f.onComplete(r => console.info(r))
is the error to lift in the Future
context and that will
get signaled as a failure in onComplete
callbacks
is an optional Scheduler reference that will get used for scheduling the actual async execution; if one isn't provided then Scheduler.global gets used, which also allows for local overrides, being a DynamicRef
Asynchronously transforms a list of futures into a future of a list.
The equivalent of Promise.all
, this is the specialized version of
Future.traverse.
Contract:
Iterable<Future<A>>
list is eagerly evaluated, transformed
from the start into an Array<Future<A>>
, so don't expect laziness in
evaluating itSample:
const f1 = Future.of(() => 1)
const f2 = Future.of(() => 2)
const f3 = Future.of(() => 3)
// Yields [1, 2, 3]
const all: Future<number[]> = Future.sequence([f1, f2, f3])
Keeps calling f
until it returns a Right
value.
Based on Phil Freeman's Stack Safety for Free.
const generate = () => {
const n = Math.random() * 1000
return n & n
}
// Keeps looping until an odd number is returned
Future.tailRecM(0, a => Future.of(() => {
return a % 2 == 0 ? Left(generate()) : Right(a)
})
is the initial seed
is the function that keeps being invoked with the previous
Left(a)
value, until a Right(b)
value is returned,
which will be the onComplete
result of the Future
reference
is an optional Scheduler reference that will get used for scheduling the actual async execution; if one isn't provided then Scheduler.global gets used, which also allows for local overrides, being a DynamicRef
Given a list of items, builds future results out of it with the specified mapping function and returns a new future that's going to be completed with the list of all generated results.
This is the generic version of Future.sequence. Useful for
processing futures in parallel, with the parallelism
factor being
configurable.
Example:
const list = [1, 2, 3, 4]
// Yields [2, 4, 6, 8]
Future.traverse(list)(a => Future.pure(a * 2))
// ... is equivalent to:
Future.sequence(list.map(_ => _ * 2))
Note that the given list
is strictly processed, so no lazy behavior
should be expected if an Iterable
is given.
But in comparison with Future.sequence, this builder has lazy
behavior in applying the given mapping function. Coupled with the
parallelism
factor, this can be used to do batched processing:
const userIDs = [1, 2, 3, 4]
// Make at most 2 requests in parallel:
Future.traverse(userIDs, 2)(fetchUserDetails)
are the values that get fed in the generator function for building a list of future results
is the maximum number of futures that are going to
be processed in parallel, defaults to Infinity
is an optional scheduler that's going to be used for scheduling the needed asynchronous boundaries
a function that takes as parameter a the generator function that's
going to map the given list
, transforming it into a list of
futures, finally returning a future that's going to complete
with the list of all asynchronously generated results
Generated using TypeDoc
A
Future
represents a value which may or may not currently be available, but will be available at some point, or an exception if the operation producing the result fails.Future<A>
is a Promise-like alternative data type, that's cancelable and lawful, inspired by Scala'sFuture[A]
.You can easily build futures out of functions, that will execute asynchronously (e.g. not on the current call stack) by means of
Future.of
:Future.of(() => 1 + 1)
Such computations use the Scheduler.global reference for execution, which can be overridden, many times in the function call, being an optional parameter (e.g. in
Future.of
), or in the local context, because it is exposed as a DynamicRef, which allows for localised overrides:import { Scheduler, GlobalScheduler, Future } from "funfix" // Custom Scheduler reference that we want to use const ec = new GlobalScheduler(false) Future.of(() => x + y, ec) // ... is equivalent with ... Scheduler.global.bind(ec, () => { Future.of(() => x + y) })
To create a
Future
out of an actual asynchronous computation, you can useFuture.create
. Here's an example that takes a function and executes it with an initial delay, returning a cancelableFuture
:import { Scheduler, Future, Try, Duration, Cancelable } from "funfix" const delay = <A>(d: Duration, f: () => A, ec: Scheduler = Scheduler.global.get()) => Future.create<A>( cb => { const task = ec.scheduleOnce(d, () => cb(Try.of(f))) return Cancelable.of(() => { console.warn("Delayed task was cancelled") task.cancel() }) }, ec )
Normally you can
await
on functions returningFuture<A>
values:async function asyncSample(n: number): Promise<number> { let sum = 0 for (let i = 0; i < n; i++) { sum += await Future.of(() => i) } return sum }
Such functions do need to return a
Promise
, because JavaScript generates code that usesPromise
's constructor. But aFuture
is "thenable", so you can await on functions returningFuture
just fine.