DEV Community

Pan Wangperawong
Pan Wangperawong

Posted on • Updated on

Async-Await vs. Then to Avoid Callback Hell 📞😈

Dante's Inferno Callback Hell

Callback Hell 🔥

When working with JavaScript, there is a concept known as callback hell. It describes a deeply nested set of callback functions that is hard to read and maintain. Callback hell happens due to the asynchronous non-blocking nature of JavaScript. Below is an illustration based on Dante's 😈 nine circles of hell.

hell() {
    firstCircle() {
        secondCircle() {
            thirdCircle() {
                fourthCircle() {
                    fifthCircle() {
                        sixthCircle() {
                            seventhCircle() {
                                eighthCircle() {
                                    ninthCircle() {
                                        alert("Hell has Frozen Over!")
Enter fullscreen mode Exit fullscreen mode


You commonly encounter callback hell when making an AJAX HTTP request. To flatten out nested callbacks for readability and maintainability, Promises can be used. With Promises, there are two techniques for flattening out our callbacks -- (1) then and (2) async-await.


This pattern helps flatten out nested callbacks into sequential thens. The entire execution of this fetch request is completely asynchronous and non-blocking.

someFunc() {
        .then((response) => response.json())
        .then((data) => console.log(data)

    console.log("I will not be blocked")
Enter fullscreen mode Exit fullscreen mode


This pattern does the same thing, but is different because each line with await causes the code execution to block while waiting for the promise to resolve.

async someFunc() {
    let response = await fetch(''),
           data = await response.json()
    console.log("I will be blocked until I get a response", data)
Enter fullscreen mode Exit fullscreen mode

async-await vs then


Useful to use if your code works with Promises and needs to execute sequentially. Due to blocking, you might lose some ability to process code in parallel. I've primarily used async-await when making API requests from a Node.js server.


This has been most useful for me when working on the client so the UI thread is not blocked since requests are being processed in parallel. If you develop your frontend with React.js, a typical use case might be to display a loading screen until a fetch request returns and then using a setState to update the UI.


Both Promise mechanisms can be used to solve the callback hell issue, each with their own optimal use cases. Don't limit yourself to any dogma. Do what makes sense for your use case.

If you found this content useful and would like to get updates on new content, follow me on Twitter @itspanw.

Top comments (2)

smolinari profile image
Scott Molinari

Correct me if I am wrong, but the console log with async/ await is only blocked, because the data variable is in it. If you had of done the same above with the .then example, that console log would be blocked too.

Or put another way, take out the data variable and the async/ await example won't block either.


sergey_solovev_ca17ee912a profile image
Leo Fitz

That's not true. Await will wait for the promise to be resolved or rejected before moving to the next line. Consider the following example:

function PromiseToWait() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("Promise 1 resolved");
    }, 5000);

console.log("Await promesses:");
const start =;
const response = await PromiseToWait();
console.log("Am I blocking?");
const end =;
console.log("Time taken: ", end - start);
Enter fullscreen mode Exit fullscreen mode

It will produce:

Image description

Whether you use response or not, it will go line by line.

I trust that this explanation has been helpful. If you have any further questions, please don't hesitate to ask.

Best regards!