I am working on something that needs nested foreach loops in order to process some data.
Intention is there is an array of ID's I need to look up, each ID related to a user and I just need to extract their names from the response of an API call. Service A has the list of ID's and then sends a HTTP GET request to service B for each ID (can't change this), which then responds with the correct info in the format of
{
success: true,
user: {
name: 'John Doe'
}
}
Code that doesn't work but is my current code
incidents.forEach((name) => {
foo = {
nameID: names
}
const asyncForEach = async (array, callback) => {
for(let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
const startMulti = async () => {
await asyncForEach(foo.nameID, async (obj, index) => {
await userController.userInfo(obj)
.then((userInfo) => {
foo.nameID[index] = userInfo.user.name
})
.then(() => {
foobarArray.push(foo)
})
})
return res.json({success: true, foobar: foobarArray})
}
startMulti()
})
Took the origional idea of nested foreach loops from this blog post which allowed me to do another before though this one won't work
https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404
edit show variables
let foo
let foobarArray = []
Added await to usercontroller now getting proper output but error message saying Cannot set headers after they are sent to the client
Names is coming from outside the code and is just needed where it is. Not sure how to explain it without explaining the project in whole/detail.
edit show code for usercontroller.userinfo
exports.userInfo = function(id) {
return new Promise((resolve, reject) => {
let user = {
_id: id
}
options.json = {user}
request.get('/userInfo', options, (err, response, body) => {
resolve(body)
})
})
}
This code work perfectly as expected - ie it sends request with proper payload and returns proper response.
Edit current code attempt
let foobarArray =[]
let names = []
let foo
for (const incident of incidents) {
foo = {
nameID: names
}
for (const id of incident.names) {
const userInfo = await userController.userInfo(id)
names.push(userInfo.user.name)
}
}
return res.json({success: true, fooreturned: foobarArray})
Error message caused by await in front of userController
SyntaxError: await is only valid in async function
edit attempt at making async function (I normally don't use async/await instead use promises)
Even after attempt code below it still gives the error message above - I had tried same code before I edited to show error message
exports.userInfo = async function(id) {
return new Promise((resolve, reject) => {
let user = {
_id: id
}
options.json = {user}
request.get('/userInfo', options, (err, response, body) => {
resolve(body)
})
})
}
Full code below except the userInfo function above which already is shown above.
exports.monitor = function(req, res, next) {
const fooID = req.params.id
let foobarArray =[]
let names = []
let foo
Incident.find({fooID})
.exec((err, incidents) => {
if(err) {
console.log(err)
return res.json({success: false, foobar: []})
}
if(incidents != []) {
for (const incident of incidents) {
foo = {
nameID: incident.foo[0].names
}
for (const id of foo.responded) {
const userInfo = await userController.userInfo(id)
names.push(userInfo.user.name)
}
}
return res.json({success: true, foobar: foobarArray})
}
})
}
That's pretty much the whole code except some logging lines I still have to add. I pretty much need the foobar: foobarArray to be an array of objects - foo - where nameID is the array of proper names not ID's. The proper names are fetched via the userController.userInfo where the ID is passed.
edit - New code after async and promisify - not sure I did the promisify correctly
exports.monitor = async function(req, res, next) {
const fooID = req.params.id
const incidents = await userController.incidentFind(fooID)
}
exports.incidentFind = async function(id) {
return new Promise((resolve, reject) => {
const sevenAgo = moment().subtract(7, 'days').toISOString()
let alertArray =[]
let names = []
let alert
Incident.find({monitorID, createdAt: {$gte: sevenAgo}})
.exec((err, incidents) => {
if(err) {
console.log(err)
return res.json({success: false, foobar: []})
}
if(incidents != []) {
for (const incident of incidents) {
foo = {
nameID: incident.foo[0].names
}
for (const id of foo.responded) {
const userInfo = await userController.userInfo(id)
names.push(userInfo.user.name)
}
}
return res.json({success: true, foobar: foobarArray})
}
})
})
}
Not sure what the actual controller monitor should contain. Bit lost
Error message
/home/me/Projects/app/incidents/controllers/users.js:100
const userInfo = await userController.userInfo(id)
^^^^^
SyntaxError: await is only valid in async function
Looks like the function should be async already (put async before function name).
I think you're looking for a simple
exports.monitor = async function(req, res, next) {
const fooID = req.params.id
const foobarArray = […]
const names = […]
try {
const incidents = await Incident.find({fooID}).exec()
for (const name of incidents) {
const foo = {
nameID: []
}
for (const userId of names) {
const userInfo = await userController.userInfo(userId)
foo.nameID.push(userInfo.user.name)
}
foobarArray.push(foo)
}
res.json({success: true, foobar: foobarArray})
} catch(err) {
console.log(err)
res.json({success: false, foobar: []})
}
}
Related
Having this async function that returns a Promise:
async function doSomething(userId) {
return new Promise(async (resolve, reject) => {
const query = 'my-query';
const roomAggregate = Room.aggregate(query).allowDiskUse(true);
Room.aggregatePaginate(roomAggregate, async function (err, data) {
if (err || !data) {
throw err;
return {
success: false,
rooms: [],
};
} else {
const rooms = [];
for (let room of data.docs) {
const roomId = room._id;
room = await computeAThing(room, {
loadWriteUps: false,
loadCreators: false,
});
room.userCompleted = await computeBThing(userId, roomId);
rooms.push(room);
}
return resolve({
rooms,
success: true,
paginator: {
// some data related to pagination
},
});
}
});
});
}
I'm not sure if it really needs to contain new Promise inside as it is already declared as async function. Is it mandatory in this case?
Because when that part was removed and at the end instead of return resolve({...}) it is only return {...} it seems to not settle.
Here is the modified code with new Promise and different return:
async function doSomething(userId) {
const query = 'my-query';
const roomAggregate = Room.aggregate(query).allowDiskUse(true);
Room.aggregatePaginate(roomAggregate, async function (err, data) {
if (err || !data) {
throw err;
return {
success: false,
rooms: [],
};
} else {
const rooms = [];
for (let room of data.docs) {
const roomId = room._id;
room = await computeAThing(room, {
loadWriteUps: false,
loadCreators: false,
});
room.userCompleted = await computeBThing(userId, roomId);
rooms.push(room);
}
return {
rooms,
success: true,
paginator: {
// some data related to pagination
},
};
}
});
}
This method is used somewhere else in this way:
const myInfo = await someObj.doSomething('myUserId');
and then checked:
if (myInfo.success) { ... }
For the first way of writing the function it works fine, for the second one myInfo is undefined and it throws and error that cannot read success of undefined.
Is something missing from the implementation?
For the second version I can see that you actually not returning anything from doSomething so I think you should do the below:
async function doSomething(userId) {
const query = 'my-query';
const roomAggregate = Room.aggregate(query).allowDiskUse(true);
const obj = Room.aggregatePaginate(roomAggregate, async function (err, data) {
if (err || !data) {
throw err;
return {
success: false,
rooms: [],
};
} else {
const rooms = [];
for (let room of data.docs) {
const roomId = room._id;
room = await computeAThing(room, {
loadWriteUps: false,
loadCreators: false,
});
room.userCompleted = await computeBThing(userId, roomId);
rooms.push(room);
}
return {
rooms,
success: true,
paginator: {
// some data related to pagination
},
};
}
});
return obj;
}
In general, you don't need to explicitly return a Promise in an async function as by default the returned value will be wrapped in a Promise , whatever it is. You just need to return something
Room.aggregatePaginat is written in coninuation-passing style, which does not interface well with promises. A generic promisify function can be used to convert any continuation-passing style function into a promise-based one. If you don't wish to write this function yourself, it is provided by Node as util.promisify -
const promisify = f => (...args) =>
new Promise((resolve, reject) =>
f(...args, (err, data) => err ? reject(err) : resolve(data))
)
Now it's easy to refactor your doSomething. Note use of Promise.all means all rooms data is processed in parallel rather than in series -
async function doSomething(userId) {
const query = 'my-query'
const roomAggregate = Room.aggregate(query).allowDiskUse(true)
const {docs:rooms} = await promisify(Room.aggregatePaginate)(roomAggregate)
return {
rooms:
await Promise.all(rooms.map(async room => ({
...await computeAThing(room, {loadWriteUps: false, loadCreators: false}),
userCompleted: await computeBThing(userId, room._id)
}))),
success: true,
paginator: ...
}
}
Also you should avoid things like { success: true } because a resolved promise is inherently "successful". A rejected one is not.
And watch out for return occurring after a throw. In this case the return is unreachable so it doesn't do what you think it's doing.
if (err || !data) {
throw err;
return { // unreachable
success: false, // these four lines
rooms: [], // are completely ignored
}; // by javascript runtime
}
Again, { success: false } goes against the Promise pattern anyway. If you want to recover from a rejection and recover with an empty list of { rooms: [] }, do it like this instead -
doSomething(user.id)
.catch(err => ({ rooms: [] }))
.then(res => console.log(res.rooms))
Better yet, you can try..catch inside doSomething and return the appropriate empty response in the event of an error. This prevents the error from bubbling up and forcing the user to handle it -
async function doSomething(userId) {
try { // try
const query = 'my-query'
const roomAggregate = Room.aggregate(query).allowDiskUse(true)
const {docs:rooms} = await promisify(Room.aggregatePaginate)(roomAggregate)
return {
rooms:
await Promise.all(rooms.map(async room => ({
...await computeAThing(room, {loadWriteUps: false, loadCreators: false}),
userCompleted: await computeBThing(userId, room._id)
}))),
paginator: ...
}
}
catch (err) { // catch
return { rooms: [] }
}
}
doSomething(user.id)
.then(res => console.log(res.rooms))
I couldn't get my for loop to run synchronously.The order of registration that it returns for each domain changes. I guess whichever answers the fastest brings those records first. Where am I doing wrong?
const rrtypes= ["A","MX","CNAME","NS","TXT"];
var resData = [];
export const getAllRecords = async (req,res) => {
const {domain} = req.params;
for await(const rrtype of rrtypes){
dns.resolve (domain, rrtype, (err, records) => {
resData.push(rrtype+" "+records);
});
}
res.send(resData);
resData = [];
}
Notice that you can use the dns.promises.resolve function: https://nodejs.org/api/dns.html#dns_dnspromises_resolve_hostname_rrtype
so you would change your code to:
const rrtypes = ["A", "MX", "CNAME", "NS", "TXT"];
export const getAllRecords = async (req, res) => {
const { domain } = req.params;
// Notice that resData should be local to this function
let resData = [];
for (const rrtype of rrtypes) {
try {
const records = await dns.promises.resolve(domain, rrtype);
resData.push(rrtype + " " + records);
// IMO this would be better: resData.push({ type: rrtype, value: records });
} catch (err) {
// Log the error here
}
}
res.send(resData);
};
The problem lies in your use of await. await should be used when calling async functions. At the moment you are using it when instantiating variables from an array.
Thanks to a comment by slebetman I was able to see this dns.resolve does not return a promise but uses callbacks to manage the async calls. As he also suggested we can fix this by creating a promise to manage these callbacks.
const rrtypes= ["A","MX","CNAME","NS","TXT"];
var resData = [];
const dnsResolvePromise = (domain, rrtype) => {
return new Promise((resolve, reject) => {
dns.resolve(domain, rrtype, (err, records) => {
if(err) return reject(err);
resolve(rrtype+" "+records);
});
})
}
export const getAllRecords = async (req,res) => {
const {domain} = req.params;
for(const rrtype of rrtypes){
try{
const records = await dnsResolvePromise(domain, rrtype);
resData.push(records);
} catch (e){
// Handle error
}
}
res.send(resData);
resData = [];
}
I am trying to loop through elements in my MongoDB database and save the values to an Array.
However, after hours of struggling I can't get it done.
Here's my code:
//shopController.js
const Product = require('../models/product');
const User = require('../models/user');
exports.getCart = (req, res, next) => {
const productIds = [];
const productsArr = [];
let userData = null;
User.findById(req.user._id)
.then(user => {
userData = user;
userData.cart.items.map(prodId => {
productIds.push(prodId.productId);
})
console.log(productIds); // [ 5dc1b6ace13a97588620d6c6, 5dc1b6ace13a97588620d6c6 ]
return productIds;
})
.then(prodIds => {
prodIds.forEach(prodId => {
Product.findById(prodId)
.then(product => {
productsArr.push(product);
})
.catch(err => console.log(err))
})
console.log(productsArr); // []
})
.catch((err) => {
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
}
Don't mind the first output, it's intended to display the same ID twice.
The result when I am logging the productsArr[] is always an empty Array, except i place the console.log(productsArr); inside the forEach() loop, which i don't want because it gets logged too often and I can't render an EJS page like this.
The render function would look like this:
res.render('shop/cart', {
path: '/cart',
docTitle: 'Cart',
products: productsArr,
userData: userData
});
I can't get the products into the productsArr[], as soon as i try to access the productsArr[] outside of the forEach() loop I got an empty Array, so I don't know how to go about this.
Do you have any advice?
Thanks in advance!
You need to render it after the Promises all resolve using Promise.prototype.all, there's no other way to get the populated productsArr array.
Try this:
//shopController.js
const Product = require('../models/product');
const User = require('../models/user');
exports.getCart = (req, res, next) => {
const productIds = [];
// const productsArr = []; not needed anymore
let userData = null;
User.findById(req.user._id)
.then(user => {
userData = user;
userData.cart.items.map(prodId => {
productIds.push(prodId.productId);
})
console.log(productIds); // [ 5dc1b6ace13a97588620d6c6, 5dc1b6ace13a97588620d6c6 ]
return productIds;
})
.then(prodIds => {
Promise.all(prodIds.map(prodId =>
Product.findById(prodId)
).then(productsArr => {
res.render('shop/cart', {
path: '/cart',
docTitle: 'Cart',
products: productArr,
userData: userData
});
console.log(productsArr); // [ ... products ... ]
}).catch(err => {
console.log(err);
})
})
.catch((err) => {
const error = new Error(err);
error.httpStatusCode = 500;
return next(error);
});
}
You are not waiting for your product promises to be finished, to display your data. You can use the Promise.all([]) function to wait for multiple promises at once.
.then(prodIds => {
Promise.all(prodIds.map(prodId => {
return Product.findById(prodId)
.then(product => {
productsArr.push(product);
})
.catch(err => console.log(err))
})).then(() => {
console.log(
})
Here we are mapping every product ids to a promise (Product.findById) and then passing all of them into the Promise.all() function, which takes an array.
We then wait for all the promise to be resolve, and call the then function.
This way, you are sure all of your promise are finished before printing your data.
I have an async function that returns a value
async getUsername(env:string,skuName: string,token:string): Promise<any> {
let value = await this.getDeviceName(env,skuName,token);
return value;
};
in another function, I am calling this get username function like this
let productN;
const prod = this.getUsername(env,skuName,token).then((resp) => {
productN= resp;
this.wait(300);
});
the variable productN is working, as i am able to see the response in my log, but when i try to use productN outside of this block, i am running into undefined.
promotion = {
name: productN,
startDate: start,
endDate: end
};
I am trying to set name to productN, and i just can not get it to work.
Can anyone explain to me what i am doing wrong please? Thanks
You can either assign to the promotion when you receive the response -
const prod = this.getUsername(env,skuName,token).then((resp) => {
productN = resp;
promotion.name = resp
this.wait(300);
});
Since your getUsername is asynchronous, you can wait for the response using await and then assign to promotion object.
(async() => {
const resp = await this.getUsername(env,skuName,token);
promotion = {
name: resp,
startDate: start,
endDate: end
}
})();
--- Edit ---
const input = [
{
env: 'lorem',
skuname: 'skuname1',
token: 'dummy'
},
{
env: 'lorem',
skuname: 'skuname2',
token: 'dummy'
}
];
const getUserName = (username) => {
return new Promise(resolve => {
setTimeout(()=>resolve(username), 2000);
});
};
(async () => {
const resp = await Promise.all(input.map(({skuname}) => getUserName(skuname)));
console.log(resp);
})();
// Or
Promise.all(input.map(({skuname}) => getUserName(skuname)))
.then(resp => {
console.log(resp);
});
try
const productN = await this.getUsername(env,skuName,token);
console.log(productN);
Can you try doing this? Return your getDeviceName function in getUsername;
getUsername(env:string, skuName: string, token:string): Promise<any> {
return this.getDeviceName(env, skuName, token);
};
const productN = await this.getUsername(env, skuName, token);
console.log(productN);
I don't know why you use getUsername. since you can get the value in productN directly from getDeviceName.
like
const productN = await this.getDeviceName(env, skuName, token);
If you want to do any other things inside getUsername. you can make it return a promise.
getUsername(env:string, skuName: string, token:string): Promise<any> {
return New Promise(async (resolve,reject)=>{
try {
let value = await this.getDeviceName(env,skuName,token);
...
//all other things you want to do here
...
resolve(value);
} catch (error) {
reject(error)
}
}
};
const productN = await this.getUsername(env, skuName, token);
console.log(productN);
nb: I used try catch block for error handling, Ignore it if you don't want to.
I have been trying to call the redisClient.del function inside a for loop and the function is not returning any state.
studentSchema.methods.generateAuthToken = function () {
let admin = this;
let access = 'student';
let token = jwt.sign({ _id: student._id.toHexString(), access }, HRCSECRET).toString();
student.tokens.forEach(element => {
console.log(element.token);
redisClient.del(element.token, (err, result) => {
if(err){
console.log(err);
}
else{
console.log(result);
}
})
});
student.tokens = [{ access, token }];
return student.save().then(() => { return token; });
};
There is no console.log statement printed during the execution
Did you try to check in Redis-cli is your stuff deleted?
Just a little refactor below
studentSchema.methods.generateAuthToken = async () => {
// your code before
const promises = []
student.tokens.forEach(element => {
promises.push(redisClient.del(element.token))
})
await Promise.all(promises)
// your next code
}