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 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
Onceis an internalIOstate that executes the giventhunkonly once, upon callingget()and then memoize its result for subsequent invocations.Returned by IO.once.