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 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.default(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.
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!)
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.
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!)
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?
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.