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.
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:
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.
As you can see here, the synchronous code is almost the same. The difference is that instead of returning the final value from
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:
soccerResults a Promise or the resolved value? Which type are you supposed to infer from that name?
Create your free account to unlock your custom reading experience.