A future of success and failure
So far we’ve made a lot of futures depending on network operations that might fail, and remote services that may not care for our input. If things don’t go as planned, the futures will fail.
Failed futures
Failed futures are messy. You may have already seen the mess created in playing around with the previous examples. Here we’ll make a big mess to see what happens, and how bad it can get.
import dispatch._, Defaults._val str = Http(host("example.com") OK as.String)
So far, so good? We’ve made a request that will fail the OK test with a redirect status code, but this failure hasn’t happened yet from the software’s perspective.
Future#print for failed futures
If we have the console print its string representation a moment later, we’ll see the problem:
scala> str.print
res0: String = Future(!Unexpected response status: 302!)
Applying failed futures
But we’re still holding have a future of string. What happens if we demand the string?
scala> str()
dispatch.StatusCode: Unexpected response status: 302
at dispatch.OkHandler$class.onStatusReceived(handlers.scala:37)
at dispatch.OkFunctionHandler.onStatusReceived(handlers.scala:29)
...
The exception was thrown in the thread that demanded the value, since there is no way to supply it.
Transforming broken futures
Broken futures carry their exceptions through transformations:
val length = for (s <- str) yield s.length
length.print
Printing yields the same result as before.
res54: String = Future(!Unexpected response status: 302!)
Deferred failed futures
And if you ask for operations on the completed future, nothing happens.
scala> for (s <- str) println(s)
How can we safely build on futures that depend on uncertain network operations?
Planning for failure
The solution is to avoid breaking futures and throwing exceptions by planning for failure. In the next pages we’ll see very simple and very rich ways of doing that.