I followed the youtube Node & Express project tutorial and Im confused facing these code: This is in async js file:
const asyncHWrapper = (fn) => {
return async (req, res, next) => {
try {
await fn(req, res, next);
} catch (error) {
next(error);
}
};
};
module.exports = asyncHWrapper;
And this is the usage:
const Task = require("../models/taskModel");
const asyncWrapper = require("../middleware/async");
const { createCustomError } = require("../errors/customErrors");
const getAllTasks = asyncWrapper(async (req, res) => {
const tasks = await Task.find({});
res.status(200).json({ tasks });
});
Im just confused about these questions:
- Does is necessary to return an arrow function in the asyncWrapper? Why doesn't just call the function?
- Where do the params (req,res) in the asyncWrapper function come from? In the "fn" function declaration?
- Why should I write two pairs of async and await in the wrapper and when I call it?
Thanks a lot!
CodePudding user response:
I hope that this answer can help you think about the concept of a "wrapper". Let's say we have a function divide:
const divide = (a,b) => a/b;
You can use this in normal code quite easily:
x = divide(10,5); // sets x to 2.
But you may decide that you care about the possibility of errors. In this case, division by zero is a possibility. You could certainly include some error handling code where you define the divide function. But you could also choose to "wrap" divide with an error handler. This way, we can keep the error handling aspects away from the main division logic. We would like to be able to define a safeDivide function like this:
const safeDivide = catchErrors(divide);
In the same way that divide
is a function that takes two arguments, safeDivide
also has to be a function that takes two arguments. So the catchErrors
wrapper will have to return a function. We will start with something like this:
const catchErrors = (fn) => {
return (p,q) => fn(p,q);
}
If you pass a function fn
to catchErrors, it will return a function. That returned function takes two arguments p
and q
, and returns fn(p,q)
. So far, this doesn't really achieve anything (except limiting the number of arguments). But now, we can add a try/catch block. I'll do it in a couple of steps, because the notation can be confusing.
The first step is to put an explicit return inside the inner arrow function.
const catchErrors = (fn) => {
return (p,q) => {
return fn(p,q);
}
}
This is technically the same code - it just looks slightly different. Now we add the try/catch.
const catchErrors = (fn) => {
return (p,q) => {
try {
return fn(p,q);
} catch (e) {
console.log("Error occurred. Continuing with null result.");
return null;
}
}
}
So now, the function returned from catchErrors
will do the same as the original function, except when an exception is thrown, in which case it will return null. (I'm not saying that this is the best general way to handle exceptions, but it's useful as an example, and it's related to the original question.)
So now look again at where we use this catchErrors
wrapper function.
const safeDivide = catchErrors(divide);
When you call the wrapper catchErrors
with function divide
, it doesn't actually do any dividing - it doesn't yet have any numbers to divide. Instead, it builds up a new function that, whenever it is called, would do the dividing, and catch any exception that arises. I hope that answers your first question.
Your second question is where req
and res
come from. They are names given to arguments that will be passed to the function. They can be passed to the wrapped function (along with a 3rd argument next
), and they will also be passed to the inner (nameless) function which includes calls to Task.find
and res.status(200)
. Those arguments will be provided by the Express (or other) web framework.
I will leave your 3rd question, on the async/await aspects of the wrapper, for another answer.
CodePudding user response:
Let me analyze the code first, and then I'll answer your question.
So here is your wrapper
const asyncHWrapper = (fn) => {
return async (req, res, next) => {
try {
await fn(req, res, next);
} catch (error) {
next(error);
}
};
};
and here is how it is used
const getAllTasks = asyncWrapper(async (req, res) => {
const tasks = await Task.find({});
res.status(200).json({ tasks });
});
the asyncWrapper
accept an fn
param, in this case, it is this function:
async (req, res) => {
const tasks = await Task.find({});
res.status(200).json({ tasks });
}
after asyncHWrapper
is called with the above function, it will return another function, in this case, the return function is assigned as the name getAllTasks
.
Now for your question:
- Does is necessary to return an arrow function in the asyncWrapper? Why doesn't just call the function?
Well basically you can
const asyncHWrapper = async (fn, req, res, next) => {
try {
await fn(req, res, next);
} catch (error) {
next(error);
}
};
And call it like this
await asyncHWrapper(async (req, res) => {
const tasks = await Task.find({});
res.status(200).json({ tasks });
}, req, res, next)
But in this case, it's just a normal function with callback, it's not a wrapper anymore.
- Where do the params (req,res) in the asyncWrapper function come from? In the "fn" function declaration?
No, it comes from getAllTasks
, your fn
is just consuming two values (req, res)
, and the next
param will be used for error handling. So when you call getAllTask
, you must pass in three params like this getAllTasks(req, res, next)
- Why should I write two pairs of async and await in the wrapper and when I call it?
I'm not sure what you meant by two pairs of async and await
. I assume you're referring to await when calling getAllTasks
and await when calling fn
?
That's just because both of them are async functions.