In Scala, values of type Future represent the result of an asynchronous computation that has not completed yet, but may either complete successfully or complete with an error in the future. (Other languages may use the term promise for very similar patterns). Futures provide us with a variety of useful methods that allow us to chain together operations, recover from errors, and take custom actions upon completion, all in a fully asynchronous, nonblocking fashion. For example, suppose we have a method, delay(duration:Duration):Future[Unit], that returns a future that will complete with the empty value Unit after duration has elapsed. If we wanted to print a message only after it completes, we could do this:
| val delayed = delay(5 seconds) |
| delayed.onComplete { |
| println("done after 5 seconds!") |
| } |
Whereas if we want to wait again and print a second message, we can use flatMap to run futures one after another:
| val delayed = delay(5 seconds) |
| delayed.flatMap { |
| println("5 seconds elapsed, now waiting 10 seconds" |
| delay(10 seconds) |
| }.onComplete { |
| println("done after 15 seconds!" |
| } |
This pattern is surprisingly common and useful in practice, appearing often in such domains as API clients, databases, and workload orchestration.
Keeping futures in mind, let’s return to our HTTP API. If our fundamental model is now a function that transforms a HttpRequest into Future[HttpResponse], we might have an underlying method and a few wrappers like so:
| private def makeRequest(Request):Future[Response] |
| |
| def get(uri:String, headers:Seq[String] = Seq()): Future[Response] = |
| makeRequest(Request(GET,uri,headers,None) |
| |
| def post(uri:String, headers:Seq[String] = Seq(), |
| body:String): Future[Response] = |
| makeRequest(Request(POST,uri,headers,Some(body)) |
| |
| def put(uri:String, headers:Seq[String] = Seq(), |
| body:String): Future[Response] = |
| makeRequest(Request(PUT,uri,headers,Some(body)) |
Then, we would be able to make asynchronous HTTP calls that look like this:
| val getRequest = get(some_uri) |
| getRequest.onComplete { response => |
| println(s"got back response code ${response.code}") |
| println(s"response body: ${response.body}") |
| } |
This design appears to strike a good balance between practicality and performance while remaining within the idioms that are familiar to Scala developers. But to implement this API and integrate it with our web server, we’ll need to go deep into the guts of Scala concurrency.