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.

Latest tech news and coding tips.