How to Escape Callback Hell with JavaScipt Promises

amberjones

AmberJ

Posted on October 21, 2019

How to Escape Callback Hell with JavaScipt Promises

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);
Enter fullscreen mode Exit fullscreen mode

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

Alt Text

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

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

💖 💪 🙅 🚩
amberjones
AmberJ

Posted on October 21, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related