The ever-growing user base and demands have forced the development community to hug asynchronous programming. As the microservices architecture is quickly gaining popularity, we can expect that asynchronous programming will become the new normal. Without blocking, asynchronous programming provides extremely high throughputs. However, programmers also scratch their heads when starting asynchronous programming even with a language with builtin asynchronous processing features such as Node.js and Go. It is okay as we all started programming by old and simple synchronous processing. But it is easy to understand how asynchronous processing works if we go to buy a coffee from Starbucks!
As Gregor Hohpe pointed out that Starbucks uses asynchronous processing. In fact, most fast food restaurants use asynchronous processing to maximize the throughput of orders (One exception is Subway, which uses pipelined execution). Let’s walk through this coffee ordering process to understand some important concepts of asynchronous processing such as non-blocking, queue, callback, out of order, future, and promise. Combined together, they enable us to perform many operations in a concurrent and efficient way.
When you place an order (e.g. cappuccino), the cashier marks a coffee cup with your name and order. The cup is placed on top of the espresso machine, lined up with other orders. Then you walk away and wait for a barista to make your cappuccino and fill the cup. By walking away, you literally “yield” the cashier to the next customer so that s/he will not be blocked. Meanwhile, you can do other things (e.g. check emails on your phone) during wait. When the coffee is ready, your name is called and then you can enjoy it.
This is an interesting processing. First of all, the time consuming part, making your coffee, doesn’t block the next customer to place an order. Besides, it doesn’t block you to do something else, either. You don’t have to stare at your cup to be filled although your could (yes, the blocking wait function). In order to make this processing non-blocking, there is a queue of cups to decouple cashier(s) and baristas. During the busy time, there are usually more baristas than cashiers to increase the throughput. Because it is asynchronous, we need a way to notify you when the coffee is ready. Yes, the barista shouts out your name. Here come the concept of callback function, i.e. a caller-provided function that is expected to be called at some convenient time (most likely in a different execution context) for delivering the result or error.
You may also notice that sometimes customers behind you get their orders earlier. Probably their orders are easier to make. Or baristas may make multiple drinks in one batch to maximize the throughput. That is, the results can be delivered out of order.
Finally, let’s look at the order itself again. After placing the order, we actually get a promise from Starbucks, i.e. our order will be filled. Correspondingly, we have a future associated with this promise. That is the cup! Although it is empty when the cashier writes your name on it, it will hold the coffee that may become available at some point. Programmatically, a promise can be thought of as a single-assignment container. So the promise and the future are just two sides of the same coin. For baristas, the cup is the promise to be filled once. But from the point view of customers, the (read/drink-only) cup is the future.
So far, we have covered all important concepts of asynchronous programming. But for simplicity, we have ignored the error handling, which Gregor’s post discussed well. If you have any questions, please leave a comment.