Skip to main content

Command Palette

Search for a command to run...

From Callbacks → Callback Hell → Promises → Async/Await (JavaScript)

Updated
2 min read
From Callbacks → Callback Hell → Promises → Async/Await (JavaScript)
S

I'm Shubham (@shubhamsinghbundela), I'm a Software Engineer, a Full-stack developer, a tech enthusiast, and a technical writer here on @Hashnode. I have a strong zeal to share my acquired knowledge and I am also willing to learn from others.

When I started learning async JavaScript, I was confused about how things evolved from callbacks to async/await.

So here’s a simple breakdown with examples

1. What is a Callback?

A callback is just a function passed into another function and executed later.

Example

function xyz(text, cb) {
  cb(text);
}

xyz("shubham", (data) => {
  console.log(data);
});

Output:

shubham

Making it Asynchronous (using setTimeout)

function xyz(text, cb) {
  setTimeout(() => {
    cb(text);
  }, 1000);
}

xyz("shubham", (data) => {
  console.log(data);
});

Output (after 1 sec):

shubham

2. Callback Hell

When multiple async tasks depend on each other, callbacks get nested.

Example

function xyz(text, cb) {
  setTimeout(() => {
    cb(text);
  }, 1000);
}

xyz("step 1", (data1) => {
  console.log(data1);

  xyz("step 2", (data2) => {
    console.log(data2);

    xyz("step 3", (data3) => {
      console.log(data3);

      xyz("step 4", (data4) => {
        console.log(data4);
      });

    });

  });
});

Problems:

  • Hard to read

  • Hard to debug

Why Promises Came

To solve callback hell, JavaScript introduced Promises.


3. Promises Version (Clean)

function xyz(text) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(text);
    }, 1000);
  });
}

xyz("step 1")
  .then((data1) => {
    console.log(data1);
    return xyz("step 2");
  })
  .then((data2) => {
    console.log(data2);
    return xyz("step 3");
  })
  .then((data3) => {
    console.log(data3);
    return xyz("step 4");
  })
  .then((data4) => {
    console.log(data4);
  })
  .catch(console.error);

Benefits:

  • No nesting

  • Better readability

  • Proper error handling


4. Async/Await Version (Best & Cleanest)

Async/Await makes async code look like normal synchronous code.

function xyz(text) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(text);
    }, 1000);
  });
}

async function run() {
  try {
    const data1 = await xyz("step 1");
    console.log(data1);

    const data2 = await xyz("step 2");
    console.log(data2);

    const data3 = await xyz("step 3");
    console.log(data3);

    const data4 = await xyz("step 4");
    console.log(data4);
  } catch (err) {
    console.error(err);
  }
}

run();

Benefits:

  • Looks synchronous

  • Easy to read

  • Cleaner error handling


5. await vs .then()

Equivalent Code

const data = await xyz("step 1");
console.log(data);

Same as:

xyz("step 1").then((data) => {
  console.log(data);
});

await is just syntactic sugar over Promises (.then), making async code look synchronous.


Final Summary

  • Callback → Nested (Callback Hell)

  • Promise → Flat chaining

  • Async/Await → Clean & readable

More from this blog

Shubham Tech. Blog's

55 posts

Problem Solver | Currently Working As a Full Stack Developer, Community Leader At @Dev_Matrix | Previously Contributor at @RealDevSquad, @TeamShiksha