Understanding Async Wrapper in JavaScript
Let’s break down the Async Wrapper concept in a simple and clear way, focusing on examples to make it more understandable for everyone, especially if you’re new to it.
1. The Problem: Handling Async Code in JavaScript
JavaScript is a single-threaded language, meaning it can only do one thing at a time. But in real-world applications, we often need to perform tasks that take time (like reading from a database, making API requests, etc.). We don’t want our app to stop and wait for these tasks, so we use asynchronous (async) code.
Async code allows us to do other things while waiting for tasks to be completed. In JavaScript, this is done using promises and the keywords async/await.
2. Async/Await Syntax
Here’s a quick example of an async function:
async function fetchData() {
try {
const data = await fetch('https://api.example.com/data');
const json = await data.json();
console.log(json);
} catch (error) {
console.error('Error fetching data:', error);
}
}
fetchData();
In this example:
await
waits for thefetch
call to finish before moving on.- If an error happens, we catch it with a try-catch block.
3. The Problem with Multiple Try-Catch Blocks
In larger projects, repeating try-catch blocks for every async function can make the code look messy and repetitive. Imagine having to wrap each async function like this:
router.get('/items', async (req, res, next) => {
try {
const items = await Item.find();
res.json(items);
} catch (error) {
next(error); // Passes error to the error handling middleware
}
});
Now imagine you have 50 routes. Writing a try-catch in every route would be inefficient and messy.
4. The Async Wrapper Solution
To clean this up, developers created the async wrapper function. Instead of writing try-catch everywhere, we can write it once inside a wrapper and reuse it. Here’s how the wrapper looks:
const asyncWrapper = (fn) => {
return async (req, res, next) => {
try {
await fn(req, res, next);
} catch (error) {
next(error); // Passes error to error handling middleware
}
};
};
This function takes another function (like a route handler) as a parameter and wraps it in a try-catch block. Now, you only need one try-catch in the entire project!
5. Using Async Wrapper
Instead of writing try-catch for every route, you can do this:
router.get('/items', asyncWrapper(async (req, res, next) => {
const items = await Item.find();
res.json(items);
}));
Now, you don’t need to write the try-catch block for each route. The asyncWrapper will catch any errors and pass them to the next()
function, which leads to your error-handling middleware.
6. Why is This Important?
- Cleaner Code: Less repeated try-catch blocks, so your code is easier to read.
- Consistent Error Handling: You can ensure that all async errors are handled the same way.
7. An Example for Better Understanding
Imagine you have two routes, one for fetching items and one for adding an item:
router.get('/items', asyncWrapper(async (req, res, next) => {
const items = await Item.find();
res.json(items);
}));
router.post('/items', asyncWrapper(async (req, res, next) => {
const newItem = await Item.create(req.body);
res.json(newItem);
}));
Both routes use asyncWrapper to avoid writing try-catch every time.
8. The express-async-errors
Package
Later, you can use the express-async-errors
package, which automatically wraps all async functions in a try-catch block without you needing to use an asyncWrapper. This simplifies your code even further.
Hope this helps you understand Async Wrapper better!
By Bhagya Wijenayake