Leaderboard
Javascript May 4, 2026
Javascript Async Return Inside Then Bug

JavaScript Async/Await — Spot the Bug!

This is a daily Javascript challenge from the CodeShot archive. Practice your knowledge of Async Return Inside Then Bug and improve your technical interview readiness.

async function getData(url) {
  fetch(url)
    .then(res => res.json())
    .then(data => {
      return data
    })
}
A fetch needs async/await not .then()
B The return inside .then() returns to .then(), not to getData — getData returns undefined
C res.json() should be res.text()
D url needs quotes

Detailed Explanation

Why This Question Matters

If you've been coding in JavaScript for a while, you've probably seen this bug. It’s one of those "invisible" errors that doesn't throw a red screen or crash your app immediately, but instead leaves you staring at undefined in your console while wondering why your API call isn't working.

The core of the problem is a fundamental misunderstanding of how async/await interacts with Promises. Many developers treat the async keyword like a magic switch that automatically makes everything inside the function wait, or they mix .then() chains with async functions without realizing they are two different ways of doing the same thing.

When you get this wrong, you aren't just writing inefficient code—you're creating race conditions and bugs that are a nightmare to debug in production.

Understanding the Code

Let's look at the snippet again:

async function getData(url) {
  fetch(url)
    .then(res => res.json())
    .then(data => {
      return data
    })
}

At first glance, it looks fine. You're calling fetch, converting the response to JSON, and returning the data. But here is the catch: the function isn't actually returning anything.

Here is what's happening internally:
1. You call getData(url). Because it's marked async, it immediately returns a Promise.
2. Inside the function, fetch(url) starts an asynchronous operation.
3. The .then() chain is set up to handle the result *whenever that operation finishes*.
4. However, the getData function itself finishes executing its synchronous block almost instantly. Since there is no return statement at the top level of the function, it returns undefined (wrapped in a Promise).

The return data inside the .then() block is returning a value to the *Promise chain*, not to the getData function. It's like sending a letter to a PO Box and expecting the post office to suddenly teleport the contents into your hand.

Finding the Correct Answer

To fix this, you have two real options. You either need to return the entire Promise chain or use await.

Option A: Returning the Promise chain

function getData(url) {
return fetch(url) // Notice the 'return' here
.then(res => res.json())
.then(data => data);
}

By adding return before fetch, the function now returns the Promise created by the fetch chain. The caller can then .then() that result.

Option B: Using await (The Modern Way)

async function getData(url) {
const res = await fetch(url);
const data = await res.json();
return data;
}

This is the correct approach for an async function. await pauses the execution of the function until the Promise resolves. The return data now actually refers to the function's output.

The original code failed because it mixed the "old" .then() syntax with the "new" async keyword without actually linking the results together.

Common Mistakes Developers Make

The biggest mistake is assuming async makes the function "wait" for any Promise inside it. It doesn't. async just ensures the function returns a Promise. You have to explicitly tell JavaScript where to pause using await.

Another common trip-up is forgetting that res.json() is *also* an asynchronous operation. I've seen plenty of senior devs write:
const data = await fetch(url).json();
This will throw an error because .json() is a method on the response object, and you have to await the result of that method call specifically.

Lastly, there's the "Silent Failure." If you forget the return or await, JavaScript won't scream at you. It will just give you undefined. This is why you should always use TypeScript or strict linting—it helps catch these "missing return" issues before they hit your staging environment.

Real-World Usage

In a production environment, you'll rarely see a fetch call this naked. You'll usually have error handling and timeouts. When you move to async/await, you get the benefit of using try/catch blocks, which are far more readable than .catch() chains.

Here is how this looks in a real-world service module:

async function getUserProfile(userId) {
  try {
    const response = await fetch(/api/users/${userId});
    
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }

return await response.json();
} catch (error) {
console.error("Failed to fetch user:", error);
return null; // Return a fallback value
}
}

Using await makes the code read linearly, which is much easier for a teammate to review during a PR than a nested tree of .then() callbacks.

Key Takeaways

- async doesn't mean "wait." It means "this function returns a Promise."
- Return your Promises. If you use .then(), you must return the chain. If you use async, you must await the result before returning it.
- .json() is async. Don't forget to await the parsing of the response body.
- Prefer async/await over .then(). It's cleaner, easier to wrap in try/catch, and looks more like the synchronous code we're used to writing.

Why this matters

Understanding Async Return Inside Then Bug is crucial for passing technical interviews. In real-world applications, this concept often leads to subtle bugs if not handled correctly. For more details, you can always refer to the official MDN Documentation.

📝
Reviewed by CodeShot Editorial
Every challenge is code-reviewed by senior developers to ensure accuracy and real-world relevance. Learn more.

Ready for your shot?

Join thousands of developers solving one logic puzzle every morning.

Solve Today's Challenge →