What's callback hell and what the hell are Promises?? To dive into those questions requires some basic understanding of the Javascript callstack, so I'll go into brief detail about that first and then navigate you through and out of callback hell.
Nature of the Beast
JavaScript is a single threaded language - meaning it has a single callstack and it can only execute one line of code at a time..
The callstack is basically a data structure which keeps track of what the program should run next. It follows the rules of FIFO - First In, First Out.
Step into a function call and it gets adds to the top of the stack, return a function and it pops off the top of the stack.
You wouldn't grab the waffle at the bottom of the stack. Neither would JavaScript.
So yeah, Javascipt has a single callstack. And this actually makes writing code simple because you don’t have to worry about the concurrency issues - or multiple computations happening at the same time.
Great!
...except when you do want stuff to happen at the same time. For example, writing web applications that make dozens of asynchronous calls to the network - you dont want to stop the the rest of your code from executing just to wait for a response. When this happens, its called holding up the event loop or "main thread".
Callback Hell
The first solution to work around JavaScript's single thread is to nest functions as callbacks.
It gets the job done, but determining the current scope and available variables can be incredibly challenging and frustrating.
And it just makes you feel like:
When you have so many nested functions you find yourself getting lost in the mist - this is whats referred to as callback hell. Its scary and no one wants to be there!
Nested callbacks tends to develop a distinct pyramid shape -
fightTheDemogorgon(function(result) {
rollForDamage(result, function(seasonsLeft) {
closeTheGate(seasonsLeft, function(finalResult) {
console.log('Hawkins is safe for ' + finalResult + ' more seasons.');
}, failureCallback);
}, failureCallback);
}, failureCallback);
And just imagine this happening even further, with 10 or 15 more nested functions calls. SCARY RIGHT??
JavaScript developers recognized this was a problem, and they created Promises.
Introduced in ES6 (2015), a Promise is an alternative way to format your asynchronous functions without breaking the event loop. It returns a special promise object that represents a future result.
Whats the Difference?
A lot of it is formatting.
Callbacks do not return anything right away, they take a function as an argument, and then you tell the executing function what to do when the asynchronous task completes.
Promises on the other hand immediately return a special promise object. They do not need a function argument, thus it does not need to be nested.
You provide the action to be taken when the asynchronous task completes using a promise method called then().
Chaining, aka the Power of Friendship
The truly AWESOME thing about Promises is that they can be chained by using their then() method when we need to execute two or more asynchronous operations back to back.
Each chained then() function returns a new promise, different from the original and represents the completion of another asynchronous step in the chain.
You can basically read it as Do this, THEN do this, THEN this.
Promises also have a catch() method. Chaining a catch() to end of a chain will give you the errors for any failed promise in the chain. Its also useful to set an action to take in the event of a failure in the chain.
Promise chaining allows us to get rid of the nasty nesting callback pattern and flatten our JavaScript code into more readable format.
fightTheDemogorgon()
.then(function(result) {
return rollForDamage(result);
})
.then(function(seasonsLeft) {
return closeTheGateIn(seasonsLeft);
})
.then(function(finalResult) {
console.log('Hawkins is safe for ' + finalResult + ' more seasons.');
})
.catch(failureCallback);
With ES6 syntax we can condense this even further!
fightTheDemogorgon()
.then((result) => rollForDamage(result))
.then((seasonsLeft) => closeTheGateIn(seasonsLeft))
.then((finalResult) => console.log('Hawkins is safe for ' + finalResult + ' more seasons.'))
.catch(failureCallback);
Defeating the Beast, Escaping Hell
The beast here being asynchronous calls, and hell being callback hell.
There is nothing stopping you from nesting Promise functions in typical callback fashion. But it's not necessary! This is usually accidentally self inflicted and is just a lack a familiarity with Promises.
You can think of Promises as callbacks in fancy new clothes. It allows asynchronous code to look cleaner, promotes ease of use and readability, most importantly, it gives you a way out of callback hell.
There is an even newer method called Async/await introduced in ES8 (2017). Check it out!
Thanks for reading!
References:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://www.youtube.com/watch?v=8aGhZQkoFbQ
Top comments (13)
Nice article.
Another clean "solution" I'd like to add is to use
async/await
:This ^
And' it supported by major browser now. :D
caniuse.com/#feat=async-functions
The poly-fills for async/await can cost a lot of kb depending on how it's transpiled and chunked.
So keep that in mind if speed and legacy is a factor.
Thanks for the addition! 😎
When there is no way to avoid callback style functions, named functions can help ease the pain.
Also, if you are using node you can use the promisify utility to convert those to promise based functions.
Definitely! Love some bluebird and util. Thanks for commenting Heiker!
Great article! We can condense that even further (no need to write the arguments here)
Becomes
That's just genius 😂
One thing worth noting regarding the shortform though, is that
actually becomes something along the lines of
rather than
which might come with unintended side effects to function scoping and what
this
actually points to.Nice catch!
Awesome post, Amber! Loved the parallelism with Stranger Things... :D
Thanks Paul!
Promises are outdated now, should check with async/await :)
you should write a post about it then
I would but that too is an old story.