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.
awaitOne of the most frequent mistakes is forgetting the await keyword when calling an asynchronous function.
async function getUser() {
const user = fetch('/api/user');
console.log(user);
}
getUser(); Promise { <pending> } Instead of the actual user data, you get a Promise object.
async function getUser() {
const response = await fetch('/api/user');
const user = await response.json();
console.log(user);
}
getUser(); Without await, JavaScript continues execution immediately and doesn’t wait for the promise to resolve.
await Outside an Async FunctionThe await keyword can only be used inside an async function (unless you’re using top-level await in supported environments).
const response = await fetch('/api/data'); SyntaxError: await is only valid in async functions async function loadData() {
const response = await fetch('/api/data');
}
try...catchPromises can fail. Ignoring error handling can crash your application.
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.
async function getData() {
try {
const response = await fetch('/invalid-url');
const data = await response.json();
return data;
} catch (error) {
console.error('Error:', error);
}
} Always wrap critical asynchronous operations in a try...catch block.
Many developers accidentally slow down their applications by awaiting tasks one after another.
const user = await fetchUser();
const posts = await fetchPosts();
const comments = await fetchComments(); Each request waits for the previous one to complete.
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]); All requests run simultaneously, significantly improving performance.
Not every function needs to be asynchronous.
async function add(a, b) {
return a + b;
} function add(a, b) {
return a + b;
} Only use async when the function performs asynchronous work.
.then() with Async/AwaitWhile technically possible, mixing styles often makes code harder to read.
async function getData() {
const data = await fetch('/api/data')
.then(response => response.json());
return data;
} async function getData() {
const response = await fetch('/api/data');
return await response.json();
} Choose one style and stick with it.
Even when returning a simple value, an async function wraps it inside a Promise.
async function getNumber() {
return 5;
}
console.log(getNumber()); Output:
Promise { 5 }
const result = await getNumber();
console.log(result); or
getNumber().then(console.log); await Inside Loops UnnecessarilyA common performance issue occurs when developers use await directly inside loops.
for (const id of ids) {
const user = await fetchUser(id);
console.log(user);
} Each request waits for the previous one.
const promises = ids.map(id => fetchUser(id));
const users = await Promise.all(promises);
console.log(users); This executes all requests concurrently.
Unhandled promise rejections can cause application instability.
async function processPayment() {
await makePayment();
} If makePayment() fails, the rejection may go unhandled.
async function processPayment() {
try {
await makePayment();
} catch (error) {
console.error('Payment failed:', error);
}
} Some developers mistakenly use await on values that aren’t promises.
const result = await 42;
console.log(result); This works, but it’s unnecessary.
const result = 42;
console.log(result); Use await only when dealing with promises.
Sometimes developers perform asynchronous operations but forget to return the result.
async function getUser() {
await fetch('/api/user');
} async function getUser() {
const response = await fetch('/api/user');
return response.json();
} Without the return statement, callers receive undefined.
Methods like forEach() don’t work well with async callbacks.
users.forEach(async user => {
await sendEmail(user);
}); forEach() does not wait for async operations.
for (const user of users) {
await sendEmail(user);
} Or:
await Promise.all(
users.map(user => sendEmail(user))
); Developers often assume successful responses.
const response = await fetch('/api/data');
const data = await response.json(); 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.
Consider this code:
const user = await fetchUser();
const profile = await fetchProfile(); Even though both operations are independent, they execute sequentially.
const userPromise = fetchUser();
const profilePromise = fetchProfile();
const user = await userPromise;
const profile = await profilePromise; Or simply:
const [user, profile] = await Promise.all([
fetchUser(),
fetchProfile()
]); try {
await operation();
} catch (error) {
console.error(error);
} Promise.all() for Independent Tasksawait Promise.all(tasks); Each async function should have a single responsibility.
if (!response.ok) {
throw new Error('Request failed');
} Stick to either:
async/await or
.then().catch() for consistency.
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.
Every application that stores and manages data relies on a set of basic operations known…
PHP remains one of the most widely used server-side programming languages, powering platforms such as…
Danfo.js is an open-source JavaScript library designed for data manipulation, analysis, and machine learning. It provides…
Pretty Good Privacy (PGP) is one of the most widely used encryption systems for securing emails,…
Database migration is one of the most challenging tasks in software engineering. While both PostgreSQL…
Modern JavaScript isn’t just let, const, arrow functions, and promises anymore. Over the years, the language has…