DEV Community

Cover image for The Secret Life of JavaScript: The Promise
Aaron Rose
Aaron Rose

Posted on

The Secret Life of JavaScript: The Promise

From Callback Hell to Async/Await — A visual evolution


Timothy stood at the chalkboard, his hand cramping. He had been writing code for ten minutes, but he was stuck in a corner—literally.

His code had drifted so far to the right side of the board that he was squeezing letters against the wooden frame.

"I am trying to do three simple things," Timothy complained. "Login, get the user's data, and then get their posts. But the code looks like a staircase to nowhere."

He stepped back to show his monstrosity:

login(user, function(token) {
    getUser(token, function(profile) {
        getPosts(profile.id, function(posts) {
            console.log("Finally got posts:", posts);
        }, function(err) {
            console.error("Error getting posts");
        });
    }, function(err) {
        console.error("Error getting user");
    });
}, function(err) {
    console.error("Error logging in");
});

Enter fullscreen mode Exit fullscreen mode

Margaret walked over, holding a fresh eraser. "Ah," she said. "The Pyramid of Doom."

"It is unreadable," Timothy said. "I get lost in the brackets. And handling errors is a nightmare."

Margaret erased the entire triangle of code. "That is because you are relying on Callbacks. You are handing control of your library to someone else and hoping they call you back."

She drew a single, clean box on the board.

"It is time you learned about The Promise."

The IOU (The Promise Object)

"A Promise," Margaret explained, "is an object. It is not the value itself. It is a placeholder—an IOU."

She wrote on the board:

  • Pending: "I am working on it."
  • Fulfilled: "Here is your data."
  • Rejected: "Something went wrong."

"Instead of passing a function into login," she said, "the login function returns a Promise object out to you. You can hold it. You can pass it around. And you can attach instructions to it."

She rewrote the first step:

const loginPromise = login(user);

loginPromise.then(function(token) {
    // This runs only when the promise is fulfilled
    return getUser(token);
});

Enter fullscreen mode Exit fullscreen mode

"Better," Timothy admitted. "But if I have three steps, don't I still nest them?"

"No," Margaret said. "Because a Promise returns... another Promise. You can chain them flat."

login(user)
    .then(token => getUser(token))
    .then(profile => getPosts(profile.id))
    .then(posts => console.log("Finally got posts:", posts))
    .catch(err => console.error("Something went wrong:", err));

Enter fullscreen mode Exit fullscreen mode

Timothy traced the line. "It’s flat. And there is only one .catch() at the end?"

"Yes," Margaret nodded. "Instead of handling errors at every single level, you catch them all in one place at the bottom. The Pyramid is gone."

The Pause (Async / Await)

Timothy looked at the chain. "It is cleaner," he agreed. "But it still looks... different. It doesn't look like normal, top-to-bottom programming."

"You are right," Margaret smiled. "The chain is still a bit 'syntactic.' If you want code that looks truly human, you need Async / Await."

She wiped the board again.

"Remember the Event Loop?" she asked. "Remember how we said you cannot block the Stack?"

"Yes," Timothy recited. "If I pause the Stack, the browser freezes."

"Correct. But await allows you to pause the function without blocking the Stack."

She wrote the modern version:

async function showUserPosts() {
    try {
        const token = await login(user);
        const profile = await getUser(token);
        const posts = await getPosts(profile.id);

        console.log(posts);
    } catch (error) {
        console.error("Something went wrong:", error);
    }
}

Enter fullscreen mode Exit fullscreen mode

Timothy stared at it. It looked impossible.

"Wait," he said. "Line 3: await login(user). The code stops there? It waits for the network?"

"The function pauses," Margaret corrected. "When the Engine sees await, it suspends this function and effectively steps aside. It returns control to the rest of the browser."

She drew a quick diagram of the Stack.

"While this function is suspended, the browser stays alive—handling clicks and rendering styles. The Stack is free."

"And when the data comes back?"

"The rest of the function—the continuation—gets dropped into the Microtask Queue (the VIP line)," Margaret explained. "As soon as the Stack is empty, your function hops back on and resumes exactly where it left off. On Line 4. With the data in hand."

The Conclusion

Timothy looked at the three versions on the board.

  1. The Pyramid (Callbacks)
  2. The Chain (Promises)
  3. The Story (Async/Await)

"They all do the same thing," Timothy realized.

"They do," Margaret said. "But the last one tells the truth. It lets you write code that looks synchronous—step by step, with a single try/catch block—while behaving asynchronously."

She handed him the chalk.

"You don't have to nest your logic anymore, Timothy. You just have to wait for it."


Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.

Top comments (4)

Collapse
 
dh336699 profile image
Scofield

In China, it's common for senior Front-end candidates to be required to manually implement a Promise from scratch during interviews.

Collapse
 
aaron_rose_0787cc8b4775a0 profile image
Aaron Rose

pretty darn impressive, isn't it. cheers Scofield! ✨💯

Collapse
 
dh336699 profile image
Scofield

Yes, it's very strict.

Collapse
 
bhavin-allinonetools profile image
Bhavin Sheth

This is such a clear and approachable way to explain promises and async/await.
The storytelling makes the transition from callbacks → promises → async/await feel natural instead of overwhelming.
I especially liked how you described await as pausing the function without blocking the browser — that’s the part many people struggle to internalize.
Great write-up, this will help a lot of developers connect the dots.