What Is a Promise?
Why do they even exist?
The longer answer is how it works in other languages. In other languages, when they do some type of I/O, like loading data from the internet, reading files, they block, or pause that line of code. The mechanics may differ per language, but the effect is the same: no other code below that line runs until that operation finishes, it fails, or someone just forcefully quits the program.
Here’s Python loading some data:
result = requests.get('https://api.github.com/user') print("done")Code language: PHP (php)
Note that line 1 will pause the program. Python will go run the HTTP call. The
result variable won’t be set yet. If the HTTP get call takes 10 years, then in 10 years, you’ll see the “done” print statement appear.
This effect compounds on itself. Watch what happens if you sleep a bunch of times:
print("What") sleep(1) print("is") sleep(2) print("up,") sleep(3) print("yo!?")Code language: PHP (php)
You’ll immediately see “What”, but the “is” takes a second. The “up” takes another 2 seconds”. It takes at least 6 seconds to see “yo!?”.
This feature in blocking languages has some pro’s and con’s. The pro is, all your code is very “easy to follow”. It’s clear what is happening and when. It’s in the order it’s listed. The con is, nothing else can happening while that blocking operation is happening.
nothing else can happening while that blocking operation is happening.
If it were written in Python, I’d have to wait 4 seconds for everything to download… maybe. If it had to load 1 thing at at time, it’d take a ton longer than 4 seconds. Then some additional time for everything to render, and ONLY then could I click a link. If I accidentally clicked a link while an advertisement was changing, I may have to wait some time too. Interacting with video players or image carousels would be worse.
Note that the “done” appears instantly, whether the
result will have the value set internally. Like a ripe avocado 🥑, except it can’t ever spoil.
sleep command although I suppose you could hack one in the browser using a type of prompt.
How do you use them?
Now that you know why they exist, how do you get that value out of it, and when do you know it’s ready? Using
The above is a typical Promise. The
fetch call is makes an HTTP GET call to some server, and at some point in the future, it’ll either give you the result or the error. Note the
catch is called for us. We don’t have to do anything, just define the callbacks and wait. If anything goes wrong in the fetch, our
catch will be called. If we screw something up in the
then, that too will fire the
catch. This is part of Promises having built in error handling (think of a try/catch that works for asynchronous code).
Often, people view Promises as just yet another call. The
fetch either gets them data, or breaks. Promises have built in deeper meanings and uses, but that’s ok; you don NOT need to know those meanings to effectively use them. Scientists still don’t necessary grok exactly how quantum mechanics works, but we did build memory chips to lock electrons in particular states to store temporary info so… you know… computers can do this thing called “work”. Ignorance is bliss and ok.
Why chain them?
then will come out of the next
then. You can define this
then yourself, or let someone else do it whoever is consuming your Promise. Note in our above HTTP call, if we want to get the JSON out, we have to parse it first by calling the
json parse method.
As long as you don’t return a Promise that has failed, ANYTHING will come out of the next
then; a resolved Promise, a boolean, some class instance,
undefined… whatever. Let’s wire that in:
Cool, but… how do we get at the parsed JSON? Well, again, we’re in a Promise, so we just create another
then where the JSON will come out:
The cool thing is if the
json method fails, or your function that messes around with the JSON fails, the single
catch handles both errors.
Why don’t people like that style anymore?
It can sure seem like there is a large movement across many blogs and social media that developers prefer the async/await style which we’ll show in a minute. Here are some of the common complaints you’ll see.
- Promises chains are verbose.
- async/await is cleaner.
- async/await results in less code.
- Promises are hard.
Each one of these have a lot in common, but I’ll cover each because I think it’s important to discuss the nuances.
Promise Chains Are Verbose
This technique of creating anonymous functions (functions that don’t have a name, also called unnamed functions or function expressions) became very common. Additionally, in the Node.js world, you’d create smaller functions that would return some type of value to be used in a stream later on. Async function? You’re going to be using a callback.
We even abandoned any function in the
catch and just passed in console.log, heh. Arrow functions do help with the verbosity aspect, especially if you remove all the whitespace I added for readability:
Async/Await is Cleaner
Programmers, myself included, are notorious for taking broad liberties with what a word means to them. Just like one man’s trash is another’s treasure, one woman’s clean code is another woman’s horribly written code. While there is a book called Clean Code, many openly disagree with it. My clean code I wrote back in my 20’s is gross to look at now, even with my historical context of “I was young, inexperienced, and given the tools I had at the time”.
However, the real reason many programmers say this is Promises are hard, and imperative code is easy for them to read and prevalent in our industry. Python, Ruby, Lua, non-heavy OOP Java, Go… they all HEAVILY follow the imperative or procedural style of coding. Revisiting our Python example:
print("What") sleep(1) print("is") sleep(2) print("up,") sleep(3) print("yo!?")Code language: PHP (php)
Fast thing, then a slow thing, then a fast thing, then a slower thing, and so on. Easy to read from top to bottom, code happens in order, and you can memorize and plan for the slow things… but that doesn’t affect the order. Line 1, 2, and 3 run in the order they’re written.
This mentality is ingrained in how many developers think, just like native English speakers who read left to right. Asynchronous programming is hard, different, and requires a lot of practice to wrap. your head around.
Writing our above in async/await style:
Much smaller. Much “easier to read”, or more accurately, “less to read”. Now, the above is 90% of async/await tutorials, but if I’m TRULY re-writing the above, it actually looks like:
Still, many procedural/imperative programmers understand how try/catch works. They can read from top to bottom, knowing if anything blows up, it’ll be inside the catch block. To them and their order of operations, non-asynchronous programming mentality, this looks cleaner.
Async/Await is Less Code
It certainly can be as you’ve seen above. Less code, while not definitive, does have lot of qualitative evidence in our industry that less code is considered better, regardless of language. That intrinsic value means async/await already before it’s used is perceived to be better. The only time async/await starts to get verbose is when you start using many try/catches when you’re trying to target a specific error, or you start nesting them, just like ifs, and you start using things like
let to compensate for potential hoisting.
… again, though, those from error prone languages like Java/C#, and in some cases Python/Ruby, that style of Exception handling may be normal for them. The await blocks fit nicely in that paradigm.
Promises Are Hard Or Aren’t Needed As Much?
Why do people still use the old style?
There are 4 reasons why I continue to use the old style.
- I’m a Functional Programmer
- Promises have built-in error handling, async/await does not
- Promises enable railway programming
- Promises enable, and will eventually be enhanced, by pipeline operators. Partial applications fit nicely here.
Second, functional programming doesn’t have a concept of throwing errors (F# has it to be friendly with their C# cousins). This means when you have errors, like Lua or Go, you return them. Unlike Go, you don’t end up with gigantic verbose procedural code; it’s just another link in the Promise chain. Async/await can’t pass errors; you’re expected to either throw, or just don’t have errors.
Third, Promises enable really advanced function composition, whether synchronous or asynchronous code. This style was really popularized when RxJS first hit the scene; and made it easier for developers to not care if code was sync or async; it just worked together seamlessly. Since a lot of what you do in Functional Programming is take some data in and return different data out, you start getting these large pipes that end up being 1 big wrapper function. Promises are perfect for that. If you change your mind later, you can just modify a
then or add a new one without affecting the public API; your
catch is still there in case something blows up, or you intentionally return a rejected Promise. This allows you to write FP code, but allow those who have no idea what you’re talking about to “just use a Promise”. “Can I use async/await?” “Sure.”
For example, if you’re parsing an Array, you may use the built-in Array.map function:
Because Promises embrace function pipelines, you can use a partial application, such as what Lodash FP offers to rewrite it:
Another, simpler example, a Promise’
catch wants a function. So most developers will do this:
or using Arrow functions:
… but, why? Why define a function just to call
console.log? Just have the Promise call it for you:
Now, using pipeline style, we can re-write our above code to:
Now, yes, you’ll need error handling, but if you’re truly writing FP style code, you won’t have errors. Using partial applications, you could change the above using Lodash/FP to:
When you see
|> think of a
.then, just less code, heh.
Why shouldn’t I mix styles?
The short answer is because it makes the code hard to read/follow. The async/await syntax is “top to bottom, 1 line after the other” whereas the Promise code is “then or catch, and I often have no idea why I’m returning things…”. Once you start mixing it, your brain has to read half the code in top to bottom style, and other parts of the code in async “all over the place style”, and then track down where the return value, if any, is. Many async/await developers do not religiously return values because they come from an Object Oriented Programming background which is full of Noops (functions that don’t return a value).
Smush all that together and you’re like “what is even going on”.
The above… did they accidentally forget to have a variable capture what
fetch returns? Do they KNOW fetch returns something? They probably want the parsed JSON, but why aren’t they returning it? If they did set a variable, they still wouldn’t get it because
result.json() isn’t returned.
The above paragraph is what your brain has to do. Hell with that. Just pick a style, and use it.
The good news? All Promises support async/await style. If you need to write async code; cool, you can use async/await if you want to. If a library is all written in Promises older style, you can use it using async/await. People consuming your code can use either style too.