softare development

Common Async/Await Mistakes Every JavaScript Developer Should Avoid

JavaScript’s async and await keywords revolutionized asynchronous programming by making asynchronous code look and behave more like synchronous code. Introduced in ES2017, they provide a cleaner alternative to callback functions and promise chains.

However, many developers—especially beginners—often make mistakes when using async/await, leading to bugs, poor performance, and unexpected behavior.

In this article, we’ll explore the most common async/await mistakes and how to avoid them.

1. Forgetting to Use await

One of the most frequent mistakes is forgetting the await keyword when calling an asynchronous function.

Wrong

async function getUser() {
    const user = fetch('/api/user');
    console.log(user);
}

getUser();

Output

Promise { <pending> }

Instead of the actual user data, you get a Promise object.

Correct

async function getUser() {
    const response = await fetch('/api/user');
    const user = await response.json();
    console.log(user);
}

getUser();

Why It Matters

Without await, JavaScript continues execution immediately and doesn’t wait for the promise to resolve.

2. Using await Outside an Async Function

The await keyword can only be used inside an async function (unless you’re using top-level await in supported environments).

Wrong

const response = await fetch('/api/data');

Error

SyntaxError: await is only valid in async functions

Correct

async function loadData() {
    const response = await fetch('/api/data');
}

3. Not Handling Errors with try...catch

Promises can fail. Ignoring error handling can crash your application.

Wrong

async function getData() {
    const response = await fetch('/invalid-url');
    const data = await response.json();
    return data;
}

If the request fails, an exception will be thrown.

Correct

async function getData() {
    try {
        const response = await fetch('/invalid-url');
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Error:', error);
    }
}

Best Practice

Always wrap critical asynchronous operations in a try...catch block.

4. Awaiting Multiple Independent Operations Sequentially

Many developers accidentally slow down their applications by awaiting tasks one after another.

Inefficient

const user = await fetchUser();
const posts = await fetchPosts();
const comments = await fetchComments();

Each request waits for the previous one to complete.

Better

const [user, posts, comments] = await Promise.all([
    fetchUser(),
    fetchPosts(),
    fetchComments()
]);

Why?

All requests run simultaneously, significantly improving performance.

5. Overusing Async/Await

Not every function needs to be asynchronous.

Unnecessary

async function add(a, b) {
    return a + b;
}

Better

function add(a, b) {
    return a + b;
}

Rule

Only use async when the function performs asynchronous work.

6. Mixing .then() with Async/Await

While technically possible, mixing styles often makes code harder to read.

Confusing

async function getData() {
    const data = await fetch('/api/data')
        .then(response => response.json());

    return data;
}

Cleaner

async function getData() {
    const response = await fetch('/api/data');
    return await response.json();
}

Choose one style and stick with it.

7. Forgetting That Async Functions Always Return Promises

Even when returning a simple value, an async function wraps it inside a Promise.

Example

async function getNumber() {
    return 5;
}

console.log(getNumber());

Output:

Promise { 5 }

Correct Usage

const result = await getNumber();
console.log(result);

or

getNumber().then(console.log);

8. Using await Inside Loops Unnecessarily

A common performance issue occurs when developers use await directly inside loops.

Slow

for (const id of ids) {
    const user = await fetchUser(id);
    console.log(user);
}

Each request waits for the previous one.

Faster

const promises = ids.map(id => fetchUser(id));

const users = await Promise.all(promises);

console.log(users);

This executes all requests concurrently.

9. Ignoring Promise Rejections

Unhandled promise rejections can cause application instability.

Bad

async function processPayment() {
    await makePayment();
}

If makePayment() fails, the rejection may go unhandled.

Better

async function processPayment() {
    try {
        await makePayment();
    } catch (error) {
        console.error('Payment failed:', error);
    }
}

10. Awaiting Non-Promise Values

Some developers mistakenly use await on values that aren’t promises.

Example

const result = await 42;
console.log(result);

This works, but it’s unnecessary.

Better

const result = 42;
console.log(result);

Use await only when dealing with promises.

11. Forgetting to Return Awaited Results

Sometimes developers perform asynchronous operations but forget to return the result.

Wrong

async function getUser() {
    await fetch('/api/user');
}

Correct

async function getUser() {
    const response = await fetch('/api/user');
    return response.json();
}

Without the return statement, callers receive undefined.

12. Using Async Callbacks Incorrectly

Methods like forEach() don’t work well with async callbacks.

Problematic

users.forEach(async user => {
    await sendEmail(user);
});

forEach() does not wait for async operations.

Better

for (const user of users) {
    await sendEmail(user);
}

Or:

await Promise.all(
    users.map(user => sendEmail(user))
);

13. Forgetting API Response Validation

Developers often assume successful responses.

Risky

const response = await fetch('/api/data');
const data = await response.json();

Safer

const response = await fetch('/api/data');

if (!response.ok) {
    throw new Error('Request failed');
}

const data = await response.json();

Always validate responses before processing data.

14. Blocking Parallel Execution Accidentally

Consider this code:

const user = await fetchUser();
const profile = await fetchProfile();

Even though both operations are independent, they execute sequentially.

Better

const userPromise = fetchUser();
const profilePromise = fetchProfile();

const user = await userPromise;
const profile = await profilePromise;

Or simply:

const [user, profile] = await Promise.all([
    fetchUser(),
    fetchProfile()
]);

Best Practices for Async/Await

✓ Always Use Error Handling

try {
    await operation();
} catch (error) {
    console.error(error);
}

✓ Use Promise.all() for Independent Tasks

await Promise.all(tasks);

✓ Keep Async Functions Focused

Each async function should have a single responsibility.

✓ Validate API Responses

if (!response.ok) {
    throw new Error('Request failed');
}

✓ Avoid Mixing Patterns

Stick to either:

async/await

or

.then().catch()

for consistency.

Conclusion

async/await makes asynchronous JavaScript significantly easier to read and maintain, but misuse can introduce subtle bugs and performance issues. By avoiding common mistakes such as forgetting await, neglecting error handling, using async callbacks incorrectly, and executing independent tasks sequentially, you can write cleaner, faster, and more reliable code.

Mastering these patterns will help you build scalable applications and become a more confident JavaScript developer.

Recent Posts

CRUD Operations: The Foundation of Data Management

Every application that stores and manages data relies on a set of basic operations known…

4 days ago

Common PHP Mistakes Every Developer Should Avoid

PHP remains one of the most widely used server-side programming languages, powering platforms such as…

4 days ago

Danfo.js: The JavaScript Data Science Library

Danfo.js is an open-source JavaScript library designed for data manipulation, analysis, and machine learning. It provides…

5 days ago

PGP Encryption And How It Works

Pretty Good Privacy (PGP) is one of the most widely used encryption systems for securing emails,…

2 weeks ago

How To Migrate from PostgreSQL to MySQL

Database migration is one of the most challenging tasks in software engineering. While both PostgreSQL…

2 weeks ago

Hidden Gems Inside Modern JavaScript

Modern JavaScript isn’t just let, const, arrow functions, and promises anymore. Over the years, the language has…

2 weeks ago