Introduces an asynchronous boundary at the current stage in the asynchronous processing pipeline (after the source has been evaluated).
Consider the following example:
const readPath: () => "path/to/file"
const io = IO.of(readPath)
.asyncBoundary()
.map(fs.readFileSync)
Between reading the path and then reading the file from that
path, we schedule an async boundary (it usually happens with
JavaScript's setTimeout
under the hood).
This is equivalent with:
self.flatMap(a => IO.shift(ec).map(_ => a))
// ... or ...
self.forEffect(IO.shift(ec))
is an optional Scheduler
implementation that can
be used for scheduling the async boundary, however if
not specified, the IO
's default scheduler (the one
passed to run()
) gets used
Handle errors by lifting results into Either
values.
If there's an error, then a Left
value will be signaled. If
there is no error, then a Right
value will be signaled instead.
The returned type is an Either value, which is what's called a "logical disjunction" or a "tagged union type", representing a choice between two values, in this case errors on the "Left" and successful results on the "Right".
// Describing an IO that can fail on execution:
const io: IO<number> = IO.of(() => {
const n = Math.random() * 1000
const m = n & n // to integer
if (m % 2) throw new Error("No odds please!")
return m
})
// By using attempt() we can observe and use errors
// in `map` and `flatMap` transformations:
io.attempt().map(either =>
either.fold(
err => "odd",
val => "even"
))
For other error handling capabilities, see IO.recoverWith and IO.transformWith.
Delays the evaluation of this IO
by the specified duration.
const fa = IO.of(() => "Hello")
// Delays the evaluation by 1 second
fa.delayExecution(1000)
is the duration to wait before signaling the final result
Delays signaling the result of this IO
on evaluation by the
specified duration.
It works for successful results:
const fa = IO.of(() => "Alex")
// Delays the signaling by 1 second
fa.delayResult(1000)
And for failures as well:
Future.raise(new TimeoutError()).delayResult(1000)
is the duration to wait before signaling the final result
Returns a new IO
that will mirror the source, but that will
execute the given callback
if the task gets canceled before
completion.
This only works for premature cancellation. See IO.doOnFinish for triggering callbacks when the source finishes.
is the IO
value to execute if the task gets
canceled prematurely
Returns a new IO
in which f
is scheduled to be run on
completion. This would typically be used to release any
resources acquired by this IO
.
The returned IO
completes when both the source and the task
returned by f
complete.
NOTE: The given function is only called when the task is complete. However the function does not get called if the task gets canceled. Cancellation is a process that's concurrent with the execution of a task and hence needs special handling.
See IO.doOnCancel for specifying a callback to call on canceling a task.
Ensures that an asynchronous boundary happens before the execution, managed by the provided scheduler.
Alias for IO.fork.
Calling this is equivalent with:
IO.shift(ec).flatMap(_ => self)
// ... or ...
IO.shift(ec).followedBy(self)
See IO.fork, IO.asyncBoundary and IO.shift.
Override the ExecutionModel
of the default scheduler.
import { ExecutionModel } from "funfix"
io.executeWithModel(ExecutionModel.alwaysAsync())
Returns a new IO
that upon evaluation will execute with the
given set of IOOptions, allowing for tuning the run-loop.
This allows for example making run-loops "auto-cancelable", an option that's off by default due to safety concerns:
io.executeWithOptions({
autoCancelableRunLoops: true
})
Creates a new IO
by applying a function to the successful
result of the source, and returns a new instance equivalent to
the result of the function.
const rndInt = IO.of(() => {
const nr = Math.random() * 1000000
return nr & nr
})
const evenInt = () =>
rndInt.flatMap(int => {
if (i % 2 == 0)
return IO.now(i)
else // Retry until we have an even number!
return evenInt()
})
Returns a new IO
that upon evaluation will execute the given
function for the generated element, transforming the source into
an IO<void>
.
Returns a new IO
that applies the mapping function to the
successful result emitted by the source.
IO.now(111).map(_ => _ * 2).get() // 222
Note there's a correspondence between flatMap
and map
:
fa.map(f) <-> fa.flatMap(x => IO.pure(f(x)))
Memoizes (caches) the result of the source IO
and reuses it on
subsequent invocations of run
.
The resulting task will be idempotent, meaning that evaluating the resulting task multiple times will have the same effect as evaluating it once.
Memoizes (caches) the successful result of the source task
and reuses it on subsequent invocations of run
.
Thrown exceptions are not cached.
The resulting task will be idempotent, but only if the result is successful.
Creates a new IO
that will mirror the source on success,
but on failure it will try to recover and yield a successful
result by applying the given function f
to the thrown error.
This function is the equivalent of a try/catch
statement,
or the equivalent of .map for errors.
io.recover(err => {
console.error(err)
fallback
})
Creates a new IO
that will mirror the source on success,
but on failure it will try to recover and yield a successful
result by applying the given function f
to the thrown error.
This function is the equivalent of a try/catch
statement,
or the equivalent of .flatMap for errors.
Note that because of IO
's laziness, this can describe retry
loop:
function retryOnFailure<A>(times: number, io: IO<A>): IO<A> {
return source.recoverWith(err => {
// No more retries left? Re-throw error:
if (times <= 0) return IO.raise(err)
// Recursive call, yes we can!
return retryOnFailure(times - 1, io)
// Adding 500 ms delay for good measure
.delayExecution(500)
})
}
Triggers the asynchronous execution.
Without invoking run
on a IO
, nothing gets evaluated, as an
IO
has lazy behavior.
// Describing a side effect
const io = IO.of(() => console.log("Hello!"))
// Delaying it for 1 second, for didactical purposes
.delayExecution(1000)
// Nothing executes until we call run on it, which gives
// us a Future in return:
const f: Future<void> = io.run()
// The given Future is cancelable, in case the logic
// decribed by our IO is cancelable, so we can do this:
f.cancel()
Note that run
takes a
Scheduler
as an optional parameter and if one isn't provided, then the
default scheduler gets used. The Scheduler
is in charge
of scheduling asynchronous boundaries, executing tasks
with a delay (e.g. setTimeout
) or of reporting failures
(with console.error
by default).
Also see IO.runOnComplete for a version that takes a callback as parameter.
a Future
that will eventually complete with the
result produced by this IO
on evaluation
Triggers the asynchronous execution.
Without invoking run
on a IO
, nothing gets evaluated, as an
IO
has lazy behavior.
runComplete
starts the evaluation and takes a callback which
will be triggered when the computation is complete.
Compared with JavaScript's Promise.then
the provided callback
is a function that receives a
Try value, a data
type which is what's called a "logical disjunction", or a "tagged
union type", a data type that can represent both successful
results and failures. This is because in Funfix we don't work
with null
.
Also the returned value is an
ICancelable
reference, which can be used to cancel the running computation,
in case the logic described by our IO
is cancelable (note that
some procedures cannot be cancelled, it all depends on how the
IO
value was described, see IO.async for how cancelable
IO
values can be built).
Example:
// Describing a side effect
const io = IO.of(() => console.log("Hello!"))
.delayExecution(1000)
// Nothing executes until we explicitly run our `IO`:
const c: ICancelable = io.runOnComplete(r =>
r.fold(
err => console.error(err),
_ => console.info("Done!")
))
// In case we change our mind and the logic described by
// our `IO` is cancelable, we can cancel it:
c.cancel()
Note that runOnComplete
takes a
Scheduler
as an optional parameter and if one isn't provided, then the
default scheduler gets used. The Scheduler
is in charge
of scheduling asynchronous boundaries, executing tasks
with a delay (e.g. setTimeout
) or of reporting failures
(with console.error
by default).
Also see IO.run for a version that returns a Future
,
which might be easier to work with, especially since a Future
is Promise
-like.
is the callback that will be eventually called with the final result, or error, when the evaluation completes
is the scheduler that controls the triggering of
asynchronous boundaries (e.g. setTimeout
)
a cancelable action that can be triggered to cancel
the running computation, assuming that the implementation
of the source IO
can be cancelled
Returns an IO
that mirrors the source in case the result of
the source is signaled within the required after
duration
on evaluation, otherwise it fails with a TimeoutError
,
cancelling the source.
const fa = IO.of(() => 1).delayResult(10000)
// Will fail with a TimeoutError on run()
fa.timeout(1000)
is the duration to wait until it triggers the timeout error
Returns an IO
value that mirrors the source in case the result
of the source is signaled within the required after
duration
when evaluated (with run()
), otherwise it triggers the
execution of the given fallback
after the duration has passed,
cancelling the source.
This is literally the implementation of IO.timeout:
const fa = IO.of(() => 1).delayResult(10000)
fa.timeoutTo(1000, IO.raise(new TimeoutError()))
is the duration to wait until it triggers the fallback
is a fallback IO
to timeout to
Creates a new IO
by applying the 'success' function to the
successful result of the source, or the 'error' function to the
potential errors that might happen.
This function is similar with .map, except that it can also transform errors and not just successful results.
is a function for transforming failures
is a function for transforming a successful result
Creates a new IO
by applying the 'success' function to the
successful result of the source, or the 'error' function to the
potential errors that might happen.
This function is similar with .flatMap, except that it can also transform errors and not just successful results.
is a function for transforming failures
is a function for transforming a successful result
Promote a thunk
function to an IO
, catching exceptions in
the process.
Note that since IO
is not memoized by global, this will
recompute the value each time the IO
is executed.
const io = IO.always(() => { console.log("Hello!") })
io.run()
//=> Hello!
io.run()
//=> Hello!
io.run()
//=> Hello!
Create a IO
from an asynchronous computation, which takes
the form of a function with which we can register a callback.
This can be used to translate from a callback-based API to a straightforward monadic version.
Constructs a lazy IO instance whose result will be computed asynchronously.
WARNING: Unsafe to use directly, only use if you know
what you're doing. For building IO
instances safely
see IO.async.
Rules of usage:
StackedCancelable
can be used to store
cancelable references that will be executed upon cancel;
every push
must happen at the beginning, before any
execution happens and pop
must happen afterwards
when the processing is finished, before signaling the
resultSuccess
or Failure
),
another async boundary is necessary, but can also
happen with the scheduler's facilities for trampolined
execution (e.g. Scheduler.trampoline
)WARNING: note that not only is this builder unsafe, but also unstable, as the IORegister callback type is exposing volatile internal implementation details. This builder is meant to create optimized asynchronous tasks, but for normal usage prefer IO.async.
Promote a thunk
function generating IO
results to an IO
of the same type.
Alias for IO.suspend.
Defers the creation of an IO
by using the provided function,
which has the ability to inject a needed Scheduler
.
Example:
function measureLatency<A>(source: IO<A>): IO<[A, Long]> {
return IO.deferAction<[A, Long]>(s => {
// We have our Scheduler, which can inject time, we
// can use it for side-effectful operations
const start = s.currentTimeMillis()
return source.map(a => {
const finish = s.currentTimeMillis()
return [a, finish - start]
})
})
}
is the function that's going to be called when the
resulting IO
gets evaluated
Given a thunk
that produces Future
values, suspends it
in the IO
context, evaluating it on demand whenever the
resulting IO
gets evaluated.
See IO.fromFuture for the strict version.
Wraps calls that generate Future
results into IO
, provided
a callback with an injected Scheduler
.
This builder helps with wrapping Future
-enabled APIs that need
a Scheduler
to work.
is the function that's going to be executed when the task
gets evaluated, generating the wrapped Future
Returns an IO
that on evaluation will complete after the
given delay
.
This can be used to do delayed execution. For example:
IO.delayedTick(1000).flatMap(_ =>
IO.of(() => console.info("Hello!"))
)
is the duration to wait before signaling the tick
Creates a race condition between multiple IO
values, on
evaluation returning the result of the first one that completes,
cancelling the rest.
const failure = IO.raise(new TimeoutError()).delayResult(2000)
// Will yield 1
const fa1 = IO.of(() => 1).delayResult(1000)
IO.firstCompletedOf([fa1, failure])
// Will yield a TimeoutError
const fa2 = IO.of(() => 1).delayResult(10000)
IO.firstCompletedOf([fa2, failure])
a new IO
that will evaluate to the result of the first
in the list to complete, the rest being cancelled
Mirrors the given source IO
, but before execution trigger
an asynchronous boundary (usually by means of setTimeout
on
top of JavaScript, depending on the provided Scheduler
implementation).
If a Scheduler
is not explicitly provided, the implementation
ends up using the one provided in IO.run.
Note that IO.executeForked is the method version of this
function (e.g. io.executeForked() == IO.fork(this)
).
IO.of(() => fs.readFileSync(path))
.executeForked()
Also see IO.shift and IO.asyncBoundary.
is the task that will get executed asynchronously
is the Scheduler
used for triggering the async
boundary, or if not provided it will default to the
scheduler passed on evaluation in IO.run
Converts any strict Future
value into an IO.
Note that this builder does not suspend any side effects, since
the given parameter is strict (and not a function) and because
Future
has strict behavior.
See IO.deferFuture for an alternative that evaluates lazy thunks that produce future results.
Returns a IO
reference that will signal the result of the
given Try<A>
reference upon evaluation.
Nondeterministically gather results from the given collection of tasks, returning a task that will signal the same type of collection of results once all tasks are finished.
This function is the nondeterministic analogue of sequence
and should behave identically to sequence
so long as there is
no interaction between the effects being gathered. However,
unlike sequence
, which decides on a total order of effects,
the effects in a gather
are unordered with respect to each
other.
In other words gather
can execute IO
tasks in parallel,
whereas IO.sequence forces an execution order.
Although the effects are unordered, the order of results matches the order of the input sequence.
const io1 = IO.of(() => 1)
const io2 = IO.of(() => 2)
const io3 = IO.of(() => 3)
// Yields [1, 2, 3]
const all: IO<number[]> = IO.gather([f1, f2, f3])
Maps 2 IO
values by the mapping function, returning a new
IO
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 IO.sequence operation and as such on cancellation or failure all pending tasks get cancelled.
const fa1 = IO.of(() => 1)
const fa2 = IO.of(() => 2)
// Yields Success(3)
IO.map2(fa1, fa2, (a, b) => a + b)
// Yields Failure, because the second arg is a Failure
IO.map2(fa1, IO.raise("error"),
(a, b) => a + b
)
This operation is the Applicative.map2
.
Maps 3 IO
values by the mapping function, returning a new
IO
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 IO.sequence operation and as such on cancellation or failure all pending tasks get cancelled.
const fa1 = IO.of(() => 1)
const fa2 = IO.of(() => 2)
const fa3 = IO.of(() => 3)
// Yields Success(6)
IO.map3(fa1, fa2, fa3, (a, b, c) => a + b + c)
// Yields Failure, because the second arg is a Failure
IO.map3(
fa1, fa2, IO.raise("error"),
(a, b, c) => a + b + c
)
Maps 4 IO
values by the mapping function, returning a new
IO
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 IO.sequence operation and as such on cancellation or failure all pending tasks get cancelled.
const fa1 = IO.of(() => 1)
const fa2 = IO.of(() => 2)
const fa3 = IO.of(() => 3)
const fa4 = IO.of(() => 4)
// Yields Success(10)
IO.map4(fa1, fa2, fa3, fa4, (a, b, c, d) => a + b + c + d)
// Yields Failure, because the second arg is a Failure
IO.map4(
fa1, fa2, fa3, IO.raise("error"),
(a, b, c, d) => a + b + c + d
)
Maps 5 IO
values by the mapping function, returning a new
IO
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 IO.sequence operation and as such on cancellation or failure all pending tasks get cancelled.
const fa1 = IO.of(() => 1)
const fa2 = IO.of(() => 2)
const fa3 = IO.of(() => 3)
const fa4 = IO.of(() => 4)
const fa5 = IO.of(() => 5)
// Yields Success(15)
IO.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
IO.map5(
fa1, fa2, fa3, fa4, IO.raise("error"),
(a, b, c, d, e) => a + b + c + d + e
)
Maps 6 IO
values by the mapping function, returning a new
IO
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 IO.sequence operation and as such on cancellation or failure all pending tasks get cancelled.
const fa1 = IO.of(() => 1)
const fa2 = IO.of(() => 2)
const fa3 = IO.of(() => 3)
const fa4 = IO.of(() => 4)
const fa5 = IO.of(() => 5)
const fa6 = IO.of(() => 6)
// Yields Success(21)
IO.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
IO.map6(
fa1, fa2, fa3, fa4, fa5, IO.raise("error"),
(a, b, c, d, e, f) => a + b + c + d + e + f
)
Returns an IO
that on execution is always successful,
emitting the given strict value.
Promote a thunk
function to a Coeval
that is memoized on the
first evaluation, the result being then available on subsequent
evaluations.
Note this is equivalent with:
IO.always(thunk).memoize()
Maps 2 IO
values evaluated nondeterministically, returning a new
IO
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 IO.gather operation. As such
the IO
operations are potentially executed in parallel
(if the operations are asynchronous) and on cancellation or
failure all pending tasks get cancelled.
const fa1 = IO.of(() => 1)
const fa2 = IO.of(() => 2)
// Yields Success(3)
IO.parMap2(fa1, fa2, (a, b) => a + b)
// Yields Failure, because the second arg is a Failure
IO.parMap2(fa1, IO.raise("error"),
(a, b) => a + b
)
Maps 3 IO
values evaluated nondeterministically, returning a new
IO
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 IO.gather operation. As such
the IO
operations are potentially executed in parallel
(if the operations are asynchronous) and on cancellation or
failure all pending tasks get cancelled.
const fa1 = IO.of(() => 1)
const fa2 = IO.of(() => 2)
const fa3 = IO.of(() => 3)
// Yields Success(6)
IO.parMap3(fa1, fa2, fa3, (a, b, c) => a + b + c)
// Yields Failure, because the second arg is a Failure
IO.parMap3(
fa1, fa2, IO.raise("error"),
(a, b, c) => a + b + c
)
Maps 4 IO
values evaluated nondeterministically, returning a new
IO
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 IO.gather operation. As such
the IO
operations are potentially executed in parallel
(if the operations are asynchronous) and on cancellation or
failure all pending tasks get cancelled.
const fa1 = IO.of(() => 1)
const fa2 = IO.of(() => 2)
const fa3 = IO.of(() => 3)
const fa4 = IO.of(() => 4)
// Yields Success(10)
IO.parMap4(fa1, fa2, fa3, fa4, (a, b, c, d) => a + b + c + d)
// Yields Failure, because the second arg is a Failure
IO.parMap4(
fa1, fa2, fa3, IO.raise("error"),
(a, b, c, d) => a + b + c + d
)
Maps 5 IO
values evaluated nondeterministically, returning a new
IO
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 IO.gather operation. As such
the IO
operations are potentially executed in parallel
(if the operations are asynchronous) and on cancellation or
failure all pending tasks get cancelled.
const fa1 = IO.of(() => 1)
const fa2 = IO.of(() => 2)
const fa3 = IO.of(() => 3)
const fa4 = IO.of(() => 4)
const fa5 = IO.of(() => 5)
// Yields Success(15)
IO.parMap5(fa1, fa2, fa3, fa4, fa5,
(a, b, c, d, e) => a + b + c + d + e
)
// Yields Failure, because the second arg is a Failure
IO.parMap5(
fa1, fa2, fa3, fa4, IO.raise("error"),
(a, b, c, d, e) => a + b + c + d + e
)
Maps 6 IO
values evaluated nondeterministically, returning a new
IO
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 IO.gather operation. As such
the IO
operations are potentially executed in parallel
(if the operations are asynchronous) and on cancellation or
failure all pending tasks get cancelled.
const fa1 = IO.of(() => 1)
const fa2 = IO.of(() => 2)
const fa3 = IO.of(() => 3)
const fa4 = IO.of(() => 4)
const fa5 = IO.of(() => 5)
const fa6 = IO.of(() => 6)
// Yields Success(21)
IO.parMap6(
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
IO.parMap6(
fa1, fa2, fa3, fa4, fa5, IO.raise("error"),
(a, b, c, d, e, f) => a + b + c + d + e + f
)
Returns an IO
that on execution is always finishing in error
emitting the specified exception.
Transforms a list of IO
values into an IO
of a list,
ordering both results and side effects.
This operation would be the equivalent of Promise.all
or of
Future.sequence
, however because of the laziness of IO
the given values are processed in order.
Sequencing means that on evaluation the tasks won't get processed in parallel. If parallelism is desired, see IO.gather.
Sample:
const io1 = IO.of(() => 1)
const io2 = IO.of(() => 2)
const io3 = IO.of(() => 3)
// Yields [1, 2, 3]
const all: IO<number[]> = IO.sequence([f1, f2, f3])
Shifts the bind continuation of the IO
onto the specified
scheduler, for triggering asynchronous execution.
Asynchronous actions cannot be shifted, since they are scheduled
rather than run. Also, no effort is made to re-shift synchronous
actions which follow asynchronous actions within a bind chain;
those actions will remain on the continuation call stack inherited
from their preceding async action. The only computations which
are shifted are those which are defined as synchronous actions and
are contiguous in the bind chain following the shift
.
For example this sample forces an asynchronous boundary
(which usually means that the continuation is scheduled
for asynchronous execution with setTimeout
) before the
file will be read synchronously:
IO.shift().flatMap(_ => fs.readFileSync(path))
On the other hand in this example the asynchronous boundary is inserted after the file has been read:
IO.of(() => fs.readFileSync(path)).flatMap(content =>
IO.shift().map(_ => content))
The definition of IO.async is literally:
source.flatMap(a => IO.shift(ec).map(_ => a))
And the definition of IO.fork is:
IO.shift(ec).flatMap(_ => source)
is the Scheduler
used for triggering the async
boundary, or if not provided it will default to the
scheduler passed on evaluation in IO.run
Keeps calling f
until a Right(b)
is returned.
Based on Phil Freeman's Stack Safety for Free.
Described in FlatMap.tailRecM
.
Shorthand for now(undefined as void)
, always returning
the same reference as optimization.
Generated using TypeDoc
Applicative
apply operator.Resembles map, but the passed mapping function is lifted in the
Either
context.