My react component componentWillMount() makes an axios call sending an array of objects. My node/express API gets the request. I want to map over the array sent, finding that users username with a mongoDB call to my User collection. I want to then create a new attribute in the object called username and set it to the result. I need to wait for my map function to finish before I sent my new mapped array back to the front end. I'm using async await and Promise.all(). My front end is receiving back an array of null objects.
I've tried just using regular promises, but had no luck there either. I understand the concept of async await by using the key term async on your method and basically waiting for whatever you use await on to move forward. Maybe I have that explanation wrong, just can't seem to figure it out. Quite new to async/await and promises.
exports.getAuthorUserNames = async (req, res) => {
if (req.body.data) {
let mappedArr = req.body.data.map(nade => {
User.findOne({ _id: nade.authorID }, function(err, result) {
if (err) {
res.sendStatus(500);
} else {
nade.username = result.username;
}
});
});
res.status(200).send(await Promise.all(mappedArr));
}
};
I except the result to return an array of objects with a new attribute called username with the username obtained from result.username(db call). I am receiving an array of nulls.
exports.getAuthorUserNames = async (req, res) => {
try{
if (req.body.data) {
const mappedArr = req.body.data.map(nade => User.findOne({ _id: nade.authorID }));
const results = await Promise.all(mappedArr);
return res.status(200).send(results);
}
} catch(e){
//handle exception here
}
};
exports.getAuthorUserNames = async (req, res) => {
if (req.body.data) {
let mappedArr = req.body.data.map(async nade => {
await User.findOne({ _id: nade.authorID }).then(result => {
nade.author = result.username;
});
return nade;
});
res.status(200).send(await Promise.all(mappedArr));
}
};
Related
I want to add data to my MongoDB collection. I'm getting this data via a local Flask API. I'm GETting the data on my React Frontend and it's displaying fine. I'm not sure why I can't do the same thing on my express nodejs backend. I want to get that same data and use it to build the entity that I'm going to store.
This is how I'm attempting to get the data
app.get('/', async (req, res) => {
let initialData = {};
axios.get('http://localhost:3000/details').then((res) => {
initialData = res.data;
});
const recruit = new RecruitModel({ email:initialData.email,
mobile_number:initialData.mobile_number,
name:initialData.name});
try {
await recruit.save()
res.send("inserted data")
} catch (error) {
console.log(error)
}
})
I'm pretty sure something wrong there and nowhere else. Because if I pass static information instead it's correctly stored, no issues.
You are saving to the database's Recruit Collection before the promise is resolved. Since data to save in the Recruit Collection is dependent upon the result from the API which will initially return the promise, therefore, use promise resolving functions to wait for its result.
Solution#1 (using .then function):
app.get('/', async (req, res) => {
let initialData = {};
try {
axios.get('http://localhost:3000/details').then((response) => {
initialData = response.data;
const recruit = new RecruitModel({
email: initialData.email,
mobile_number: initialData.mobile_number,
name: initialData.name,
});
recruit.save().then((response) => res.send('inserted data'));
});
} catch (error) {
console.log(error);
}
});
Solution#2 (using async await keywords):
app.get('/', async (req, res) => {
try {
const response = await axios.get('http://localhost:3000/details');
const recruit = new RecruitModel({
email: response.data.email,
mobile_number: response.data.mobile_number,
name: response.data.name,
});
await recruit.save();
res.send('inserted data');
} catch (error) {
console.log(error);
}
});
Either solution will work in your case.
I was trying to use async/await functionalities and my problem is that I have an async function which I need to return some data from and inside it I nested 3 await functions where the last one returns the value for the whole function, but I could not do that because the last await is on find query from mongodb and it returns the found element only. So I wanted to know if there is some way to to that data from that await function.
async register_Employee_Credential (id,req,res){
try{
let employee_credential= new Employee_credential({
employee: id,
username: req.body.username,
password: req.body.password
});
await bcrypt.genSalt(10,async (err,salt)=>{ //first await function
await bcrypt.hash(employee_credential.password,salt, async (err,hash)=>{ //second await function
if(err) console.log("error while generating salt");
employee_credential.password = hash;
result = await Employee_credential.create(employee_credential,async (err,result)=>{ // third await function
if(err)
{
var errMessage = await help.Property_Validator(err);
return errMessage; // this is the return message i need
}
})
})
})
return errMessage; //this is the final return for the calling function
}catch(err){
console.log("employee creditial error furthur: " + err);
}
}
An async function ALWAYS returns a promise. That is built into the language and you cannot change it. So, you can never return a plain value from an async function.
Further a plain asynchronous callback in your function gets called long AFTER the function has already returned that promise so you cannot return a value from the function from within that callback.
Further yet, await only does anything useful when you are awaiting a promise so your await in this await Employee_credential.create() does nothing useful. The same with each of your other await statements. They are not awaiting promises so they do nothing useful. Please read about how await actually works and is used rather than just sticking it places hoping it solves some problem. It only works properly when used in a very specific way. You need to learn that.
And, finally, you can NEVER return an asynchronously retrieved value directly from a function. Javascript just doesn't work that way. You will have to communicate back the return value via a callback, a promise or an event.
You will need to either promisify all asynchronous functions within your async parent function so that you can use await with them or you will need to stop using async/await and communicate back the return result via a callback.
Since none of your three asynchronous operations in your function directly support promises, then the least amount of change to your code woudl be to add a callback argument to your function and go the traditional callback method of communicating back asynchronous results. You then pass the callback into the function and get the result inside that callback when it is called.
register_Employee_Credential(id, req, res, callback) {
try {
let employee_credential = new Employee_credential({
employee: id,
username: req.body.username,
password: req.body.password
});
bcrypt.genSalt(10, async (err, salt) => {
if (err) {
console.log(err);
return callback(err);
}
bcrypt.hash(employee_credential.password, salt, async (err, hash) => {
if (err) {
console.log(err);
return callback(err);
}
employee_credential.password = hash;
Employee_credential.create(employee_credential, async (err, result) => {
if (err) {
var errMessage = help.Property_Validator(err);
callback(errMessage);
} else {
callback(null, result);
}
})
})
})
} catch (err) {
console.log("employee creditial error furthur: " + err);
callback(err);
}
}
If you want to use promises, then bcrypt appears to already have a promise interface built in if you do NOT pass it a callback so you just have to promisify the .create() method and can do that like this:
const {promisify} = require('util');
async register_Employee_Credential(id, req, res) {
let employee_credential = new Employee_credential({
employee: id,
username: req.body.username,
password: req.body.password
});
// promisify the create method
Employee_credential.createP = promisify(Employee_credential.create);
let salt = await bcrypt.genSalt(10);
let hash = await bcrypt.hash(employee_credential.password, salt);
employee_credential.password = hash;
try {
let result = await Employee_credential.createP(employee_credential);
return result;
} catch(err) {
let errMessage = help.Property_Validator(err);
throw errMessage;
}
}
You would use the async/promise version by calling:
register_Employee_Credential(id, req, res).then(result => {
console.log(result);
}).catch(err => {
console.log(err);
});
Right now i have this code
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
Posts.list().then(data=> {
var jsonOutput=JSON.stringify(data)
postData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Events.list().then(data=> {
var jsonOutput=JSON.stringify(data)
eventData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Messages.list().then(data=> {
var jsonOutput=JSON.stringify(data)
messageData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
Users.list().then(data=> {
var jsonOutput=JSON.stringify(data)
userData=jsonOutput //this doesnt work
})
.catch(erro => res.status(500).send('error'))
//Then when all data from colections is retrieve i want to use the 4 variables that i created in the beggining
});
So basicly im trying to retrieve the data from my mongo database and then assign the results to that 4 variables that i create, but im not getting success.
For what i´ve been seeing i have to use async but im having some trouble doing it.
I don't like too much mrlanlee solution. This is a typical situation where using async / await can really make sense. Anyway, the Hugo's solution (the second one, with async await), even if it just works, will make the four queries in sequence, one after another to. If you want a clean, working and parallel solution, check this:
router.get('/export', async function(req, res, next) {
let data
try {
data = await Promise.all([
Posts.list(),
Events.list(),
Messages.list(),
Users.list()
]);
// at this point, data is an array. data[0] = Posts.list result, data[1] = Events.list result etc..
res.status(200).json(data)
} catch (e) {
res.status(500).send('error');
}
});
The other answer from Sashi is on the right track but you will probably run into errors. Since your catch statement on each promise returns 500, if multiple errors are caught during the query, Express will not send an error or 500 each time, instead it will throw an error trying to.
See below.
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
try {
postData = Posts.list().then(data=> {
return JSON.stringify(data);
});
eventData = Events.list().then(data=> {
return JSON.stringify(data)
});
messageData = Messages.list().then(data=> {
return JSON.stringify(data);
})
userData = Users.list().then(data=> {
return JSON.stringify(data)
});
} catch (err) {
// this should catch your errors on all 4 promises above
return res.status(500).send('error')
}
// this part is optional, i wasn't sure if you were planning
// on returning all the data back in an object
const response = {
postData,
eventData,
messageData,
userData,
};
return res.status(200).send({ response })
});
For explanation of why you weren't able to mutate the variables, see Sashi's answer as he explains it.
The variables defined outside the async code is out of scope of the async functions. Hence you cannot store the returned value from the async functions in those variables.
This should work.
router.get('/export', function(req, res, next) {
var postData, eventData, messageData, userData
postData = Posts.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
eventData = Events.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
messageData = Messages.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
userData = Users.list().then(data=> {
var jsonOutput=JSON.stringify(data);
return jsonOutput;
}).catch(erro => res.status(500).send('error'));
});
Using Async/Await is a much neater solution.
router.get('/export', async function(req, res, next) {
var postData, eventData, messageData, userData;
try{
postData = await Posts.list();
eventData = await Events.list();
messageData = await Messages.list()
userData = await Users.list();
catch (e){
res.status(500).send('error');
}
});
I have the following Express endpoint:
const all = require('promise-all');
router.post('/verify', upload.single('photo'), async (req, res) => {
...
await all({'p1': p1, 'p2': p2}).then((response) => {
...
console.log("Response:",
ruleCtrl.manageRule(detection, res);
});
});
ruleCtrl.manageRuleis as follows:
export async function manageRule(identifierDetected, res) {
let rule = db.getRule(identifierDetected);
await all([rule]).then((ruleExtracted) => {
...
res.json(ruleExtracted);
}).catch((err) => {
res.status(418).send("DOCUMENT_NOT_RECOGNIZED");
});
}
and db.getRule:
export async function getRule(idRule) {
return new Promise((resolve, reject) => {
Rule.findOne({ruleID: idRule}, (err, rule) => {
if (err) {
reject("MongoDB Rule error: " + err);
} else {
resolve(rule);
}
});
})
}
My response is into manageRule and this function depends of the values extracted into the await all. So, right now, Express is returning a response before get the information from mongoose database (db).
Which is the way to handle this issue?
Thanks everyone!
I would refactor your code a bit to make it easier to read, and also return the result from ruleCtrl.manageRule(detection, res);.
The request might simply be timing out since your original code is missing a return there or an await (to make sure it finishes executing)
Express endpoint:
const all = require('promise-all');
router.post('/verify', upload.single('photo'), async (req, res) => {
...
// Catch any exceptions from the promises. This is the same as using .catch
try {
// Lets assign the returned responses to variable
let [p1Result, p2Result] = await all({'p1': p1, 'p2': p2});
...
console.log("Responses:", p1Result, p2Result);
// return the response from manageRule method
return ruleCtrl.manageRule(detection, res);
} catch(err) {
// Handle err here
}
});
One of the great benefits with async await is moving away from chained promises, so simply return the result from the await to a variable instead of using .then()
ruleCtrl.manageRule
export async function manageRule(identifierDetected, res) {
// Use try catch here to catch error from db.getRule. Assign to variable and return
// res.json
try {
let ruleExtracted = await db.getRule(identifierDetected);
...
return res.json(ruleExtracted);
} catch(err) {
return res.status(418).send("DOCUMENT_NOT_RECOGNIZED");
}
}
You dont have to return res.json or res.status here, I just like to keep track of when I want to end function execution.
You could refactor the ruleCtrl.manageRule method even further by not sending in res as a parameter but by returning the result from db.getRule instead. Let router.post('/verify) handle req and res, so to make it even easier to read.
I originally had try...catch in my getAllUsers method for querying but ended up removing it because as far as I could tell it wasn't doing anything. I know the async function returns a promise so it should be fine and actually based on how the code is structured I think it's required otherwise the try...catch in the query would swallow the error. Is there anything I'm missing with this structure and use of async/await, try...catch, and .then .catch?
let getAllUsers = async () => {
let res = await models.users.findAll({
attributes: [ 'firstName', 'lastName' ]
});
return res;
};
router.get(`${path}`, (req, res) => {
queries.users.getAllUsers()
.then(users => {
res.status(200).json(users);
})
.catch(error => {
res.status(500).send(error)
});
});
There's just no reason to use await at all in your function. Instead of this:
let getAllUsers = async () => {
let res = await models.users.findAll({
attributes: [ 'firstName', 'lastName' ]
});
return res;
};
It can just be this:
let getAllUsers = () => {
return models.users.findAll({
attributes: [ 'firstName', 'lastName' ]
});
};
You just return the promise directly and the caller uses the promise the same as you already were. Since you are not using the result within your getAllUsers() function or coordinating it with anything else, there's no reason to use await. And, since there's no use of await, there's no reason for the function to be declared async either.
If you wanted to use await, you could use it for the caller of getAllUsers() like this:
router.get(`${path}`, async (req, res) => {
try {
let users = await queries.users.getAllUsers();
res.status(200).json(users);
} catch(error => {
res.status(500).json(error);
}
});
And, here you would have to use try/catch in order to catch rejected promises. Personally, I don't see how this is particularly better than what you had originally with .then() and .catch() so for a situation as simple as this (with no coordination or serialization with other promises), it's really just a matter of personal preference whether to use .then() and .catch() or await with try/catch.
You would use async/await with the code that calls getAllUsers rather than using it in getAllUsers itself:
const getAllUsers = () => {
return models.users.findAll({
attributes: [ 'firstName', 'lastName' ]
});
};
router.get(`${path}`, async (req, res) => {
try {
const users = await queries.users.getAllUsers();
res.status(200).json(users);
} catch (error) {
res.status(500).send(error)
}
});
The best way I have found to handle this is using middleware.
Here is the function:
// based upon this
// http://madole.xyz/error-handling-in-express-with-async-await-routes/
// https://github.com/madole/async-error-catcher
export default function asyncErrorCatcher(fn) {
if (!(fn instanceof Function)) {
throw new Error("Must supply a function");
}
return (request, response, next) => {
const promise = fn(request, response, next);
if (!promise.catch) {
return;
}
promise.catch((error) => {
console.log(error.message);
response.sendStatus(500);
});
};
}
Here is the usage:
router.get("/getSettings/", asyncErrorCatcher(async (request: Request, response: Response) => {
const settings = await database.getSettings();
response.json(settings);
}));