A couple, hooking their fingers as if they are making a promise
Let’s recap the main purpose Promises in one sentence:
Add synchronous properties to async code
As you probably know, Promises were created to make it possible for async functions to inherit synchronous properties, such as being able to return values and throw errors.
Let’s pretend we are in a parallel universe where Promises would never be required, and we could retrieve data from two resources using synchronous code in JavaScript without any performance drawbacks.
One resource contains the soccer matches, and another one contains the soccer results. We want to convert the data structure into a single array that contains all soccer matches and the results.
Be aware that for the purpose of this example, we want to load one request after the other sequentially, not parallelly.
In an ideal world, the code would look something like this:
A code example showing the fetching of soccer matches and soccer results using an imperative style
Pretty straightforward, it gets the soccerMatches
from a resource, correlates the data with the soccerResults
, and then print both the score and the match name in the console.
But that doesn’t work.
Unfortunately, due to how JavaScript was built, we can’t do that, or at least not today. We need to do something else, something different. And here is where Promises come in:
A code example showing the fetching of soccer matches and soccer results using the Promises style
As you can see here, the synchronous code is almost the same. The difference is that instead of returning the final value from fetchSoccerMatches*
and _fetchSoccerResults*_
, we return a promise that it will resolve with the desired value later. Note that, after the code was changed to use Promises, the **soccerMatches**
variable was renamed to **fetchSoccerMatches**
. The reason is that the variable doesn’t hold the data anymore, it holds a concept instead, a “promise” that the data will come later.
This distinction of how to name the variable is very important.
If you hold the reference to a Promise in a variable and keep treating that variable as if it was the resolved value, then the next developer that will read the code will have trouble to distinguish the expected value from the expectation to get that value. This violates the Principle of Least Astonishment, in which “a component of a system should behave in a manner consistent with how users of that component are likely to expect it to behave”. So, in this case, when working with Promises, the naming is very important in order to make the intent clear.
Treating Promise references as the resolved value will make it difficult to distinguish the expected value from the expectations to get that value.
Using variable names to treat values as if they were the same with or without Promises is a mistake. While a Proxy does not change an existing component’s interface and can be treated as if you were working with the original, a Promise does.
In other words… Promises are not Proxies.
The problem above does not make itself clear when working with a small code base, only when you start working with a bigger system, where there is a mix of variables that were already retrieved from async data sources and others that should be retrieved later. Depending on the context you can’t tell, by reading the code, if the variable is a promise or the resolved value. This can be dangerous because it can make a bug go unnoticed in a code review by inducing the reviewer to interpret that the duck typed variable is the correct resolved value, when it is not.
When you are reading the code written by somebody else (or even yourself from the past), you shouldn’t need to understand the whole context of what is happening in the code. A small piece of code should be able to express clearly a small piece of the business logic without too much effort.
With that in mind, let's take the example above and take it out of context. The only thing you need to know is that the module where this code lies creates a list of matches with the proper results:
A subset of the first code example showing the fetching of soccer matches from a resource using the Promises style
Is soccerResults
a Promise or the resolved value? Which type are you supposed to infer from that name?
Naming things is hard, but treating Promise objects as objects created with the Proxy pattern is an indicative of misunderstanding, which can cause bugs that cannot be easily spotted.
Do not assume that Promises should be treated the same way as the final data structure, they are just utilities to work around the inefficiency of asynchronous data handling in JavaScript.
Thanks for reading. If you have some feedback, reach out to me on Twitter, Facebook or Github.