Introduction
Asynchronous programming in JavaScript is crucial for building efficient and responsive web applications. However, improper use of asynchronous constructs can lead to performance issues and hard-to-debug errors. Here are several best practices to help you write better asynchronous JavaScript code.
Avoid Using async in the Promise Constructor
Bad Practice
// ❌
new Promise(async (resolve, reject) => {});
Good Practice
// ✅
new Promise((resolve, reject) => {});
Explanation:
Using async within the Promise constructor can lead to unnecessary wrapping of Promises. Additionally, if an async function inside the Promise constructor throws an exception, the constructed Promise will not reject, making it impossible to catch the error. Instead, use a regular function and handle the asynchronous operations within it.
Avoid await Inside Loops
Bad Practice
// ❌
for (const url of urls) {
const response = await fetch(url);
}
Good Practice
// ✅
const responses = [];
for (const url of urls) {
const response = fetch(url);
responses.push(response);
}
await Promise.all(responses);
Explanation:
Using await inside a loop can prevent JavaScript from fully leveraging its event-driven nature. This approach executes fetch requests sequentially, which can be slow. Instead, initiate all fetch requests concurrently and use Promise.all to wait for their completion, significantly improving execution efficiency.
Do Not Return From the Promise Constructor Function
It is not recommended to return a value from the Promise constructor function, as the value returned will be ignored and can cause confusion in the codebase.
Bad Practice
// ❌
new Promise((resolve, reject) => {
return someValue;
});
Good Practice
// ✅
new Promise((resolve, reject) => {
resolve(someValue);
});
Explanation:
Returning a value from the Promise executor function does not have any effect. Instead, use resolve to fulfill the promise with the intended value.
Use try...catch for Error Handling in async Functions
Proper error handling is critical in asynchronous code to ensure that your application can gracefully handle failures.
Good Practice
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch error:', error);
// Handle the error appropriately
}
}
Explanation:
Using try...catch blocks within async functions allows you to handle errors gracefully, ensuring that unexpected issues do not crash your application.
Avoid Creating Unnecessary Promises
Creating unnecessary promises can add complexity and reduce the readability of your code.
Bad Practice
// ❌
async function example() {
return new Promise(async (resolve) => {
const result = await someAsyncFunction();
resolve(result);
});
}
Good Practice
// ✅
async function example() {
return await someAsyncFunction();
}
Explanation:
If you are already working within an async function, there is no need to wrap another async function in a Promise. The async keyword automatically returns a promise.
Conclusion
By adhering to these best practices, you can write more efficient, readable, and maintainable asynchronous JavaScript code. Avoid using async within Promise constructors, refrain from using await inside loops, ensure proper error handling, and prevent the creation of unnecessary promises. These guidelines will help you leverage the full potential of JavaScript’s event-driven architecture, resulting in better-performing applications.
That's all for today.
And also, share your favourite web dev resources to help the beginners here!
Connect with me:@ LinkedIn and checkout my Portfolio.
Explore my YouTube Channel! If you find it useful.
Please give my GitHub Projects a star ⭐️
Thanks for 22525! 🤗
Top comments (5)
I would change some things from that post tbh
for:
Here it comes the explanation:
Avoiding the await
The await keyword breaks the async and makes the runtime effectively wait till every promise is done (bad yada yada) it's way better to let it be and send a callback to do whatever when they decide to
resolve
orreject
.Here's a quick example you may've seen in React:
Promise.all vs Promise.allSettled
Following the previous point, both
Promise.all
andPromise.allSettled
"convert" an iterable (usually an array) of promises into a single promise which will behave different depending on the API you use;If you use
Promise.all
the entire promise rejects whenever a single one of the promises fails.We usually don't want that. E.G. when loading data for 6 components, if a single component failed or timed out or whatever I'd rather get the other 5 working and show an error in just one of them.
In these common situations
Promise.allSettled
comes to help. It will only reject if all executed promises reject, otherwise it will resolve (then control the unhappy path in the component instead).Other changes:
On the other hand, this:
Will break the async nature of it, so I don't know how can it be a good practice! ❌ This code is effectively calling an async function just to make it wait till another thing finishes, making it synchronous 💀
It also breaks your advice of using
Promise.resolve
instead ofreturn
😅 is this post generated with AI?There are several ways to fix that like sending a function as reference and executing it as a callback once the async thingy has resolved or rejected;
side note: you might also want to Promise.resolve() that in a way or another depending on your app logic.
!important
Please, I know that wrapping your head around asynchronous stuff is complicated but double check the statements (both using the logic notation you learn in the college/uni and/or by simply running your code) so other newbies don't get wrong advice.
As a rule of thumb, whenever you find yourself converting async code in a sync execution, you're doing something wrong.
We have
state
and other tools in React which you can use for control flow asynchronously (not to mention new sweets from React 19).Solid JS has it probably even better with Signals, which are now available in Angular as well. In vanilla JS you may use object proxies to check if a variable has changed or not, making it easy to run other code when it gets updated by an asynchronous process...
Best,
Joel
If you found my content helpful or interesting, and you’d like to show your appreciation, why not buy me a coffee? It’s a small gesture that goes a long way in helping me keep creating more content for you.
Just click the button below to support:
Thanks Joe for sharing your knowledge with us. Appreciate it!!!!
Thanks for 22525! 🤗
Some comments may only be visible to logged-in visitors. Sign in to view all comments.