-
Notifications
You must be signed in to change notification settings - Fork 57
Result Channels
A result-channel represents a single potential value, as opposed to a normal channel which represents a stream of values. For instance, (read-channel channel)
will return a result-channel representing the next message from the channel.
> (def ch (channel))
#'ch
> (def next-msg (read-channel ch))
#'next-msg
> next-msg
<< ... >>
Since the channel is empty, the result-channel representing the next message from the channel has no value. However, once we enqueue a message into the channel, next-msg
will be realized.
> (enqueue ch "hello")
true
> next-msg
<< "hello" >>
To pull the value out of a result-channel, you can simply dereference it using @result-channel
. If the result-channel hasn’t been realized yet, then the thread will block until it is. An asynchronous alternative is to use (on-success result-channel & callbacks)
, which will trigger the callbacks once the result-channel has been realized.
Once a result-channel is realized its value cannot be consumed. If you repeatedly register callbacks on a realized result-channel with on-success
, the callbacks will always be triggered.
A result-channel can also represent a failure to realize a value. For instance, read-channel
allows a timeout to be specified. Let’s see what happens if we specify a timeout of 0 ms when reading from an empty channel:
> (read-channel (channel) 0)
<< ERROR: java.util.concurrent.TimeoutException: read-channel timed out after 0 ms >>
The result-channel now contains an exception. If we subsequently enqueue a message into the channel, it will not be consumed as the operation has already failed. If we dereference this result-channel, its exception will be thrown.
We may hook into this outcome using the (on-error result-channel & callbacks)
. If using on-success
, it’s generally prudent to also handle the error outcome. However, on-success
and on-error
are not the recommended ways to interact with result-channels. Instead, Lamina provides several higher-level abstractions for using result-channels effectively.
Pipelines represent the composition of functions, any of which may return a result-channel. If a function returns an unrealized result-channel, then the pipeline pauses until the realized value can be passed into the next function. Since the pipeline may not immediately complete, invoking a pipeline also returns a result-channel.
If any function in a pipeline throws an exception or returns a result-channel which emits an exception, then the pipeline is short-circuited and the result-channel returned by the pipeline emits the exception.
Wrapping a block of code in the async macro allows result-channels in its body to be treated like real values. This can be a powerful tool, allowing values be computed locally, on other threads, or even other systems without affecting the structure of your code.