Skip to Content
Course content

58: Callbacks and Callback Hell

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was messing around with a mock API the other day, trying to build a simple user profile viewer. I had a function that simulated fetching a user from a database. It looked something like this:

function getUser(id) {
  setTimeout(() => {
    console.log("Found user: Dave");
    return { id: id, name: "Dave" };
  }, 1000);
}

const user = getUser(1);
console.log("The user is: " + user.name);

Wait, where did the data go?

When I ran that, I expected to see "Found user: Dave" and then "The user is: Dave". Instead, I got a TypeError telling me I can't read property 'name' of undefined. I looked at the console, and "The user is: undefined" printed before "Found user: Dave" even showed up.

The problem is that getUser doesn't actually return anything immediately. The return statement is tucked inside the setTimeout callback, which doesn't execute until the timer finishes. By the time that timer goes off, the rest of the script has already finished running. JavaScript didn't wait for the database; it just kept moving.

Handing off the baton

Since I can't force the function to "wait" without blocking the whole browser, I have to change my approach. Instead of asking the function to give me the data, I'll give the function a set of instructions on what to do once it has the data. This is the core idea of a callback.

Let's rewrite it. I'll add a second parameter to getUser—a function that I'll call once the data is ready:

function getUser(id, callback) {
  setTimeout(() => {
    console.log("Found user: Dave");
    const user = { id: id, name: "Dave" };
    callback(user); // Here is where we "call back" the provided function
  }, 1000);
}

getUser(1, (user) => {
  console.log("The user is: " + user.name);
});

Now it works. I'm essentially saying, "Hey, go get the user, and when you're finally done, run this piece of code I'm handing you." It feels a bit backwards at first, but it's the only way to handle asynchronous events without freezing the UI.

The sliding scale of indentation

This works great for one request. But real apps aren't that simple. What if I need the user's name, then I need to use that user's ID to fetch their latest posts, and then I need to fetch the comments for the first post? Each of these is an async request.

I tried chaining them together, and this is what happened to my code:

getUser(1, (user) => {
  getPosts(user.id, (posts) => {
    getComments(posts[0].id, (comments) => {
      console.log("The first comment is: " + comments[0].text);
      // Imagine if we needed to fetch the author of that comment...
      // And then the author's profile picture...
    });
  });
});

Looking at this mess

Take a look at that structure. The code is literally sliding off the right side of my screen. We've hit what the community calls "Callback Hell" (or the "Pyramid of Doom").

Beyond just looking ugly, this is a nightmare to maintain for a few reasons:

  • Error Handling: If getPosts fails, where do I catch that error? I'd have to add an error check inside every single nested level.
  • Readability: The logic flows horizontally instead of vertically. It's hard to track where one operation ends and the next begins.
  • Fragility: If I decide I want to swap the order of two requests, I have to carefully move multiple sets of closing braces and parentheses without breaking the whole chain.

We'll look at ways to flatten this out in future lessons, but for now, recognize this pattern. When you see your code starting to look like a sideways triangle, you've officially entered Callback Hell.




📋 Practical Task

Refactoring the Nested Order-Tracking System

You have been handed a piece of legacy code that tracks a customer's order. The current code is written in a deeply nested callback style, making it hard to read. Your task is to implement the missing logic in the final callback to complete the chain.

The Goal: 1. Fetch the order. 2. Use the order ID to fetch the shipping status. 3. Use the shipping status to fetch the current GPS coordinates of the delivery truck. 4. Finally, log a message: "Your package is currently at [coordinates]".


// Mock API functions (Do not modify these)
function getOrder(orderId, callback) {
  setTimeout(() => callback({ id: orderId, item: "Mechanical Keyboard" }), 500);
}

function getShippingStatus(orderId, callback) {
  setTimeout(() => callback({ status: "In Transit", truckId: "TRK-99" }), 500);
}

function getTruckLocation(truckId, callback) {
  setTimeout(() => callback({ coords: "40.7128° N, 74.0060° W" }), 500);
}

// YOUR TASK: 
// Complete the callback chain below to log the coordinates.
getOrder(123, (order) => {
  getShippingStatus(order.id, (shipping) => {
    // Write your code here to get the truck location 
    // and log the final message.
  });
});
Rating
0 0

There are no comments for now.

to be the first to leave a comment.