Synchronous blocking calls
First, let’s finish talking about what is synchronous.
Synchronous communication requires that each end of an exchange of communication responds in turn without initiating a new communication.
Each successive transmission of data requires a response to the previous transmission before a new one can be initiated.
When calling external services from our applications over HTTP, we naturally think of these requests as something synchronous, since HTTP is a synchronous protocol in itself.
Thus, we perform a request and wait for its response; then, we make another request and wait, and so on.
This leads to bottlenecks. Therefore, the bigger our number of requests, the slower we will respond. In addition to this, the request is blocking the execution thread until a response is given by the server.
Hence, we have another bottleneck in performance, since we won't be able to perform any further task until the request is fulfilled.
Asynchronous calls
As opposed to what we discussed before, asynchronous calls are by definition non-blocking (asynchronous non-blocking as usually seen, it’s for clarification only and also redundant.)
This means the actual request will fire up the execution of the task on “background” while another thread will handle the response with callbacks and events for the different outcomes.
By doing this, we have the possibility to continue performing tasks until we get the reply to our request, as well as improved performance and reduced bottlenecks.
We can achieve this behavior using libraries like AsyncHttpClient (Java) which manages a single thread to get the responses, plus it does not have some of the limitations OkHttp has.
For example, regardless of its asynchronous callbacks, it creates a new thread for every call and leaves it blocked waiting for the response and since it manages a limited thread pool internally, you can run out of available threads very easily.
For example, as seen here, we can make asynchronous (non-blocking) calls without using futures:
Another way to do this is to use Java's CompletableFuture<T>, like this:
Conclusion
If we want to get the most out of our calls to downstream dependencies or services and prevent that from becoming the culprit of latencies and bottlenecks, we need to make our calls asynchronous and avoid having blocking operations.