Designing an Asynchronous API

Fortunately, we’ve done a lot of the basic analysis of HTTP already. The fundamental structure of an HTTP Request and Response is unchanged:

HTTPClient/httpclient/HTTPClient.scala
 case​ ​class​ HttpRequest(
  method​:​​String​,
  uri​:​​String​,
  headers​:​​collection.Map​[​String​, ​String​],
  body​:​​String​)
 case​ ​class​ HttpResponse(
  code​:​​Int​,
  headers​:​​collection.Map​[​String​, ​String​],
  body​:​​String​)

However, the signature of our previous HTTP client methods is no longer appropriate. Have a look at the following function:

def send(request:Request):Response

A function like this will block the calling thread until send() returns. In the context of an asynchronous program, this can have many negative consequences. And on a single-threaded event loop such as what libuv provides, it can be catastrophic—a blocking function will prevent any new connections or requests from being handled until it returns! To get around this limitation, we need some way to rephrase the operation of the HTTP client so that it’s compatible with an asynchronous event loop.

For the server we built in Chapter 5, Writing a Server the Old-Fashioned Way, we defined handlers with callback functions that provided a way for us to say, “When this happens, do that.” Although client APIs can be designed in this way as well, it’s not always convenient, especially for a program that makes many different kinds of external requests. Instead, what you probably want is a way to tell your program, “Start doing this, and when it completes, then do that.”

Although there are a variety of techniques to solve this kind of problem, the standard approach in Scala is the future pattern, as implemented by the class Future in Scala’s standard library.