Skip to main content

Command Palette

Search for a command to run...

Async/Await — The "Syntactic Sugar" That Saved JS

Published
3 min read

If you’ve ever looked at a nested chain of .then() and .catch() and felt like you were reading a "Pyramid of Doom," you understand why we needed a change. Async/Await was introduced in ES2017 to make asynchronous code look and behave like synchronous code.

1. What is "Syntactic Sugar"?

In development, "Syntactic Sugar" refers to a feature that doesn't add new functionality to the engine but provides a much cleaner way to write existing logic.

  • Under the hood: async/await is still just Promises.

  • On the surface: It reads like a top-to-bottom script, making it easier for our human brains to process.

2. The async Keyword: The Promise Wrapper

When you put async before a function declaration, you are telling the JavaScript engine two things:

  1. This function will always return a Promise.

  2. If you return a non-promise value (like a string or number), JS will automatically wrap it in a Promise.resolve().

  return "System Online"; 
}

// Equivalent to:
// function getStatus() { return Promise.resolve("System Online"); }

getStatus().then(val => console.log(val)); // "System Online"

3. The await Keyword: The "Pause" Button

The await keyword can only be used inside an async function. It tells the engine: "Pause the execution of this specific function until this Promise settles (resolves or rejects)."

The beauty? It doesn't block the Main Thread. While this function is "paused," the rest of your app (the UI, other functions) keeps running smoothly.

4. Readability: Promises vs. Async/Await

Let’s look at a "Senior" refactor of a standard API call.

The "Old" Promise Way:

  fetch('/api/user')
    .then(res => res.json())
    .then(user => {
       console.log(user.name);
       return fetch(`/api/posts/${user.id}`);
    })
    .then(res => res.json())
    .then(posts => console.log(posts))
    .catch(err => console.error("Deployment Failed", err));
}

The "Modern" Async/Await Way:

  try {
    const userRes = await fetch('/api/user');
    const user = await userRes.json();
    
    const postRes = await fetch(`/api/posts/${user.id}`);
    const posts = await postRes.json();
    
    console.log(posts);
  } catch (err) {
    console.error("System Crash:", err);
  }
}
  • Insight: Notice how the try/catch block replaces the .catch(). This is huge for Error Handling because we can now use the same error-handling patterns we use for synchronous code.

5. Why the "Senior" Dev Prefers This

  • Debugging: When you set a breakpoint in an async/await function, the debugger moves line-by-line. In a Promise chain, the "jumping" between .then() callbacks makes stack traces messy.

  • Conditionals: Writing an if/else statement inside a Promise chain is a nightmare. In async/await, it's just standard logic.

  • Memory: It's often more efficient for the engine to optimize a single async context than multiple nested callback closures.

Feature

Promises (.then)

Async/Await

Code Style

Chained/Callback-based

Linear/Procedural

Error Handling

.catch()

try/catch (more robust)

Debugging

Difficult (stepped execution)

Easy (line-by-line)

Mental Model

"When X is done, do Y"

"Wait for X, then proceed to Y"