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

pan profile image Pan Wangperawong Updated on ・2 min read

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!")


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")


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('https://someurl.com'),
           data = await response.json()
    console.log("I will be blocked until I get a response", data)

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.

Posted on by:

pan profile

Pan Wangperawong


Amazon Alexa Developer Advocate | Ex: Microsoft, NASA, Stanford, USC | @ItsPanW | *Opinions are my own


markdown guide

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.