hi guys i am new in node and have this simple question , what is difference between this two sniped
Note: i know async / await functionality and also in front-end application it is best practice for handle async action but in node in working with mongoose i want know which approach better for handle
first solution
// for example we pass this function as controller to route handler
exports.myController = async (req, res, next) => {
try {
const data = await Model.find();
const some = await new Model(data).save();
} catch(e) {
next(e);
}
}
second solution
exports.myController = (req, res, next) => {
const data = Model.find((err, data_) => {
const some = new Model(data_);
some.save((err, result) => {
....
})
});
}
i want to know when i have an error from mongoose , in second way can throw error like this
// in callback function
if (err) {
throw Error();
}
but how can i handle this in async/await solution
you simply throw it or log it in your catch block:
try {
const data = await Model.find();
const some = await new Model(data).save();
} catch(e) {
throw e;
next(e);
}
the async/await is working like promises but without no nesting callbacks and it throws an error synchronous to what errors first and stop from executing the other lines.
Based in your edited Note:
you should always go for the async solution way in nodejs or even for anything related to javascript it's the best composable and reusable solution.
Related
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!
The youtube tutorial link :Node.js / Express Course - Build 4 Projects
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 write it like this
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, and its name shouldn't be asyncHWrapper.
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.
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.
I have a simple controller for adding new users. After the successful resolution (user added), the controller sends a 202 response. As you can see, the function is using then/catch and is not using async/await.
const addUserController = function (req, res, next) {
Users.addOne(req.userid, req.body.email)
.then(() => {
res.status(202).send();
})
.catch((err) => {
console.log(err);
res.status(500).json({ message: "Internal server error." });
});
};
When I am testing this function in Jest with the, the function executes immediately, without going to the then() part, resulting in a mistaken 200 code, instead of 202, so the following test fails:
it("Should add a user", () => {
let req, res, next, pool;
pool = new Pool();
req = httpsMocks.createRequest();
res = httpsMocks.createResponse();
res.next = null;
req.userid = 1;
req.body = {
id: 2
}
pool.query.mockResolvedValue({rows:[], rowCount: 1});
apiController.addUserController(req, res, next);
expect(res.statusCode).toBe(202);
expect(pool.query).toBeCalledTimes(1);
});
However, when I make it like that:
it("Should add a user", async () => {
let req, res, next, pool;
pool = new Pool();
req = httpsMocks.createRequest();
res = httpsMocks.createResponse();
res.next = null;
req.userid = 1;
req.body = {
id: 2
}
pool.query.mockResolvedValue({rows:[], rowCount: 1});
await apiController.addUserController(req, res, next);
expect(res.statusCode).toBe(202);
expect(pool.query).toBeCalledTimes(1);
});
that is I add async/await, it works alright - the response status code is 202, meaning the function was awaited and the test passes.
But why? When I hover over the newly added 'await' VS code is suggesting that
'await' has no effect on the type of this expression.
Well it makes sense - it should have no effect, as the tested function is not async, so it shouldn't work, but well, it works - only when I add the async/await to the Jest function it works fine.
Could someone explain this to me?
I add async/await, it works alright - the response status code is 202, meaning the function was awaited and the test passes. But why?
No, as you concluded from the missing return value, the function is not awaited. Your code is equivalent to
apiController.addUserController(req, res, next);
await undefined;
Now, why does it still make a difference? Because with the await, the test is waiting a tiny bit before running the expect() calls, and that tiny bit is enough for your mocked pool to return a value and have the first .then() handler be executed.
However, you now basically introduced a race condition. Having a longer promise chain in the addUserController would make the test fail. Maybe even a test for the 500 status being created in .then().catch() might already not work.
This is very fragile, don't write a test like that. One solution would be to simply return the promise chain from addUserController and await it - as long as no other callers will be confused by that, it's the simple fix. Another solution is to actually wait for the mock response to be actually sent. If I read the docs of node-mocks-http right, something like this should work:
it("Should add a user", async () => {
const { once, EventEmitter } = require('events');
const pool = new Pool();
const req = httpsMocks.createRequest();
const res = httpsMocks.createResponse({ eventEmitter: EventEmitter });
res.next = null;
req.userid = 1;
req.body = {
id: 2
}
pool.query.mockResolvedValue({rows:[], rowCount: 1});
const responseEndPromise = once(res, 'end');
apiController.addUserController(req, res, next);
await responseEndPromise;
expect(res.statusCode).toBe(202);
expect(pool.query).toBeCalledTimes(1);
});
Writing the backend API for my app I've encountered countless examples of people using both forms of handling errors in their endpoint functions, as presented below:
Option 1:
export const deleteProject = asyncHandler(async(req, res) => {
const project = await Project.findByIdAndDelete(req.params.id);
if(project) {
res.status(204).send();
} else {
res.status(404).json({message: "Project not found"});
throw new Error('Project not found');
}
});
Option 2:
export const deleteBoard = asyncHandler(async(req, res) => {
try {
await Board.findByIdAndDelete(req.params.id);
res.status(204).send();
} catch(err) {
res.status(404).json({message: "Board not found"});
throw new Error('Board not found');
}
});
To my current understanding, both of those are correct. I wanted to ask - is one of them preferable because of reasons I might not be aware of?
I prefer option number #2.
Remember findByIdAndDelete can give n types of error so. you need to change your code like this.
export const deleteBoard = asyncHandler(async(req, res, next) => {
try {
const board = await Board.findByIdAndDelete(req.params.id);
if(board)
return res.status(204).send();
res.status(404).json({message: "Board not found"});
} catch(err) {
// don't throw anything here
// throw new Error('Board not found');
// use next(errmsg) which will call global error handler
next(err);
}
Since you don't need the item after delete, you can use a better function deleteOne or deleteMany.
const deleted = await Board.deleteOne({ _id: req.params.id }).exec();
if (deleted.deletedCount)
return res.status(204).send();
res.status(404).json({message: "Board not found"});
The try-catch statement should be executed only on sections of code where you suspect errors might occur, and due to the overwhelming number of possible circumstances, you cannot completely verify if an error will take place, or when it will do so. In the latter case(Option 2), it is presented appropriately to use try-catch.
for mongoose query use then block at the end of api and you can easily handle it like
await table.findByIdAndDelete({...})
.then(obj => {
return res.status()//check on the response from query basis
})
.catch(err => {
res.status(401).send({msg: "Error in db"})
})
Reason
by using then block your axios wait until he did't get a response from api and by using then and catch block the api don't send any response until query is not executed. In your option-1 what you do if mongoose in is running and Node send the response before query execution as node is a single threaded.
I have some difficulties with a nested promise (below).
For now I'm using an async function in the catch to trigger authentication Errors.
But is it really a good way and isn't there a better way ?
The function have to send a POST request. If an authentication Error is thrown then login(), else throw the error.
If login() is fulfilled : retry the POST (and then return the results), else throw the error;
function getSomeData() {
return post('mySpecialMethod');
}
function login() {
const params = { /* some params */ }
return post('myLoginMethod', params).then(result => {
/* some processing */
return { success: true };
});
}
const loginError = [1, 101];
function post(method, params, isRetry) {
const options = /* hidden for brevity */;
return request(options)
// login and retry if authentication Error || throw the error
.catch(async ex => {
const err = ex.error.error || ex.error
if (loginError.includes(err.code) && !isRetry) { // prevent infinite loop if it's impossible to login -> don't retry if already retried
await login();
return post(method, params, true)
} else {
throw err;
}
})
// return result if no error
.then(data => {
// data from 'return request(options)' or 'return post(method, params, true)'
return data.result
});
}
Use
getSomeData.then(data => { /* do something with data */});
I'd suggest that for complex logic at least you use the async/await syntax.
Of course .then() etc is perfectly valid, however you will find the nesting of callbacks awkward to deal with.
My rule (like a lot of things in programming) is use context to make your decision. .then() works nicely when you're dealing with a limited number of promises. This starts to get awkward when you're dealing with more complex logic.
Using async / await for more involved logic allows you to structure your code more like synchronous code, so it's more intuitive and readable.
An example of two approaches is shown below (with the same essential goal). The async / await version is the more readable I believe.
Async / await also makes looping over asynchronous tasks easy, you can use a for loop or a for ... of loop with await and the tasks will be performed in sequence.
function asyncOperation(result) {
return new Promise(resolve => setTimeout(resolve, 1000, result));
}
async function testAsyncOperationsAwait() {
const result1 = await asyncOperation("Some result 1");
console.log("testAsyncOperationsAwait: Result1:", result1);
const result2 = await asyncOperation("Some result 2");
console.log("testAsyncOperationsAwait: Result2:", result2);
const result3 = await asyncOperation("Some result 3");
console.log("testAsyncOperationsAwait: Result3:", result3);
}
function testAsyncOperationsThen() {
return asyncOperation("testAsyncOperationsThen: Some result 1").then(result1 => {
console.log("testAsyncOperationsThen: Result1:", result1);
return asyncOperation("testAsyncOperationsThen: Some result 2").then(result2 => {
console.log("testAsyncOperationsThen: Result2:", result2);
return asyncOperation("testAsyncOperationsThen: Some result 3").then(result3 => {
console.log("testAsyncOperationsThen: Result3:", result3);
})
})
})
}
async function test() {
await testAsyncOperationsThen();
await testAsyncOperationsAwait();
}
test();
... But is it really a good way and isn't there a better way ?
No it's not a good idea because it hurts code readability.
You're mixing 2 interchangeable concepts, Promise.then/catch and async/await. Mixing them together creates readability overhead in the form of mental context switching.
Anyone reading your code, including you, would need to continuously switch between thinking in terms of asynchronous flows(.then/.catch) vs synchronous flows (async/await).
Use one or the other, preferably the latter since it's more readable, mostly because of it's synchronous flow.
Although I don't agree with how you're handling logins, here's how I would rewrite your code to use async/await and try...catch for exception handling:
function getSomeData() {
return post('mySpecialMethod')
}
async function login() {
const params = { } // some params
await post('myLoginMethod', params)
return { success: true }
}
const loginError = [1, 101]
async function post(method, params, isRetry) {
const options = {} // some options
try {
const data = await request(options)
return data.result
} catch (ex) {
const err = ex.error.error || ex.error
if (err) throw err
if (loginError.includes(err.code) && !isRetry) {
await login()
return post(method, params, true)
}
throw err
}
}
I obviously cannot/didn't test the above.
Also worth exploring the libraries which provides retry functionalities.
something like https://www.npmjs.com/package/async-retry
Generally this is not a big problem. You can chain/encapsulate async calls like this.
When it comes to logic it depends on your needs. I think the login state of a user should be checked before calling any API methods that require authentication.
I'm using paypal-node-SDK library to make calls to Paypal API. It uses a promise like this:
paypal.payment.create(create_payment_json, function (error, payment) {
if (error) {
throw error;
} else {
console.log("Create Payment Response");
console.log(payment);
}
});
However I'm trying to make it async because my other functions are async/await as well. But it doesn't return any callback, just undefined.
exports.create = wrap(async(req, res) => {
const payment = await paypal.payment.create(create_payment_json);
});
//wrap
module.exports = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch((error) => {
console.log(error);
res.status(400).send({success: false, message: error.message});
});
};
};
It seems the library supports promises (feature: link), but the cb(null, response) doesn't really return anything when it is a async function. Am I missing something? Is there a way to make it work async?
You need beta / version 2.0+ to use promises in the sdk.
Not sure exactly what your wrap is, but for node styled callbacks function foo(a,b,callback) you can use promisify
const { promisify } = require('util');
exports.create = promisify(paypal.payment.create);
A manual conversion of paypal.payment.create would be
function create(create_payment_json){
return new Promise(function(resolve,reject){
paypal.payment.create(create_payment_json,function(error,payment){
if(error){
reject(error);
}else{
resolve(payment);
}
}
};
}
Which can then be used by const payment = await create(json);
Then in your router you can use something like
router.get('/', async function (req, res, next) {
try{
const payment = await create(json);
res.send(payment);
}catch(e){
console.log(e);
}
});
Extending on the answer by Cody G..
It is correct you need to upgrade to the beta v2+ to use promises in the sdk, although if you wish to do so, after you've upgraded you will find there are breaking changes.
You can read the full documentation here:
https://github.com/paypal/PayPal-node-SDK/tree/2.0-beta
There is also a migration guide to easily transition from v1 to v2:
https://github.com/paypal/PayPal-node-SDK/blob/2.0-beta/docs/Migrating.md