Async Model Data
In real applications you quite often have to deal with model data that is asynchronously loaded or updated. Usual approach would be to use Option[A]
to
indicate whether data is there or not, but then you cannot deal with error situations. Using Either[ErrorType, Option[A]]
or Try[Option[A]]
gives your some
control over errors, but if you consider refreshing old data and getting an error back, what should the result be? Not to mention knowing how long the async
operation has been running and notifying the user when it has been delayed for too long. Clearly the existing data containers are not enough for this.
Potential Data
A
Pot[+A]
represents potential data that may exist in seven different states as seen in the picture on the right. Pot
provides you with the same interface as
Option
or Try
, making it very easy to use in your existing application code. Things like for comprehension, getOrElse
or even recover
are supported.
Like Option
, Pot
is immutable and you have to create a new Pot
when the state or content changes.
Pot
usually starts as an Empty
(option None
) or Ready
(option Some
), depending on whether you have the data available or not. You can use the empty
method to create a correctly typed Empty
.
var myData = Pot.empty[String] // myData is now Empty
When you make an async call to fetch data, change the state to Pending
with the help of pending
function.
myData = myData.pending() // myData is now Pending
Assuming you get a successful response from the server, you can change the state to Ready
.
myData = myData.ready(data) // myData is now Ready(data)
In case something went wrong, you can store the exception in the Failed
state.
myData = myData.fail(exception) // myData is now Failed(exception)
When you are ready to refresh your Ready
data, you again call pending
but this time it results in a PendingStale
indicating that old data is still there to be
used while new data is fetched.
myData = myData.pending() // myData is now PendingStale(data)
In case the async call gets new results, the state goes to Ready(newData)
but if something goes wrong, you'll end up with FailedStale
containing the
old data.
myData = myData.fail(exception) // myData is now FailedStale(data, exception)
If the data you are trying to fetch does not exist, you can use Unavailable
to indicate that.
myData = myData.unavailable() // myData is now Unavailable
Usage
Pot
exposes its state via following functions.
def isEmpty: Boolean
def nonEmpty = !isEmpty
def isPending: Boolean
def isStale: Boolean
def isFailed: Boolean
def isReady: Boolean
def isUnavailable: Boolean
def state: PotState
You can query specific state via isX
functions, or use state
for matching. Note that isEmpty
means there is no data in the Pot
, so states Unavailable
,
Pending
and Failed
, in addition to Empty
, all return true
.
Otherwise a Pot
works like an Option
(Empty
, Unavailable
, Pending
and Failed
work like None
).
Pending Time
The pending states of Pot
store a time stamp when created. You can access this through startTime
, or you can directly get the duration of the operation
with duration(currentTime)
. This is useful in an user interface, where you'll want to show an indicator if the operation is taking too long. If
an operation fails and you retry it, the startTime
will remain the same, giving you the total duration of the retried operations.