how to use promises in forEach loops node? - node.js

I have a page that displays user contacts. it retrieves an array of _ids from a specific user and the should iterate over these arrays to get corresponding contact information. However I get stuck with my async operations. In the current situation it resolves after the firs contactinformation was pushed to the invitationsFromFriendsData array. If I put the resolve outside the forEach loop, it resolves instantly without adding any contactinformation to the array. What am I doing wrong?
exports.contactBoard = async (req, res) => {
const username = req.params.username;
const doc = await User.findOne({'username': username}).exec();
const friendFiller = async () => {
return new Promise((resolve, reject)=> {
doc.invitationsFromFriends.forEach (async element => {
const doc1 = await User.findById(element).exec()
doc.invitationsFromFriendsData.push(doc1.username)
resolve('gelukt')
});
})};
const send = async () => {
return new Promise((resolve, reject)=> {
res.status(200).json({
message:"retrieved user contacts",
contacts: doc
})
console.log("Nu verzenden")
resolve ('gelukt')
})
};
const verzend = async() => {
let first = await friendFiller();
console.log('Ik wacht op het vullen')
let second = await send();
}
verzend();
}

You can use Promise.all to await an array of promises.
const friendFiller = async () => {
const friends = await Promise.all(doc.invitationsFromFriends.map((id) => User.findById(element).exec()));
friends.forEach(() => {
doc.invitationsFromFriendsData.push(doc1.username);
})
}
Having said that I think Mongoose can perform "joins" on your behalf with populate which I expect is more efficient as it'll issue a single DB query for all the friends.

Related

async await question in Firebase Firestore

I tried to get the document and the subcollection data at once in firestore.
And I used the async and await to deal with the forEach loop.
It still has some problem. The console.log 4 always executes first.
But what I expect the should be 1 -> 2 -> 3 -> 4.
Could anyone help me how to redesign my code?
let data = {};
toolboxesRef.get()
.then(async snapshot => {
let toolboxes = [];
// get toolbox
await snapshot.forEach(async doc => {
let toolbox = {};
await toolboxesRef.doc(doc.id).collection('tags').get()
.then(snapshot => {
let tags = []
snapshot.forEach(doc => {
tags.push(doc.id);
console.log(1)
})
toolbox.tags = tags;
toolbox.id = doc.id;
toolbox.data = doc.data();
console.log(2)
})
console.log(3)
toolboxes.push(toolbox)
})
console.log(4);
data.toolboxes = toolboxes
return data;
})
export const asyncForEach = async (dataSnapshot, callback) => {
const toWait = [];
dataSnapshot.forEach(childSnapshot => {
toWait.push(childFunction((childSnapshot)));
});
await Promise.all(toWait);
};
Hey i updated the code because it seems that Firebase integrate it's own foreach function. Then in order to resolve i decided to call every function and store the promise that it return into an array then i use Promise.all to resolve an array of async function
You are using async operations inside forEach which doesn't work as you expect thm. You need to either use for..of or Promise.all. Try this version
const snapshot = await toolboxesRef.get();
const toolboxes = [];
for(const doc of snapshot) {
const toolbox = {};
const snapshot1 = await toolboxesRef.doc(doc.id).collection("tags").get();
const tags = [];
snapshot1.forEach(doc => {
tags.push(doc.id);
console.log(1);
});
toolbox.tags = tags;
toolbox.id = doc.id;
toolbox.data = doc.data();
console.log(2);
console.log(3);
toolboxes.push(toolbox);
}
console.log(4);
data.toolboxes = toolboxes;
return data;
You might need to tweak few things here and there but you will get an idea.

How to achieve a one to many relationship with GraphQL & DataLoader

I'm having a hard time figuring out why my graphQL & DataLoader setup isn't working and could use some help.
I have a User and a Orchestra type, and I would like to transform a User to populate its createdOrchestras field and do the same thing with Orchestra and an owner field.
EDITED.
The following code causes an infinite loop:
Here are the DataLoaders, which are passed to the resolvers via context:
const DataLoader = require('dataloader');
const Orchestra = require('../models/orchestras');
const User = require('../models/users');
const batchOrchestras = async ids => {
const orchestras = await Orchestra.find({ _id: { $in: ids } });
const orchestraMap = {};
orchestras.forEach(o => {
orchestraMap[o.id] = o;
});
return ids.map(id => orchestraMap[id] || new Error(`No result for ${id}`));
}
const batchUsers = async ids => {
const users = await User.find({ _id: { $in: ids } });
const userMap = {};
users.forEach(u => {
userMap[u.id] = u;
});
return ids.map(id => userMap[id] || new Error(`No result for ${id}`));
};
module.exports = () => new DataLoader(batchUsers)
module.exports = () => new DataLoader(batchOrchestras);
Here are the transform functions which should be capable of fetching data for nested fields via data loaders and modify sensitive fields like the user password.
async function transformUser(userId, loaders) {
const user = await loaders.userLoader.load(userId.toString());
return {
...user._doc,
createdOrchestras: await Promise.all(
user._doc.createdOrchestras.map(orchestra =>
transformOrchestra(orchestra, loaders)
)
)
}
}
async function transformOrchestra(orchestraId, loaders) {
const orchestra = await loaders.orchestraLoader.load(orchestraId.toString());
return {
...orchestra._doc,
owner: transformUser(orchestra._doc.owner, loaders)
}
}
module.exports = {
transformUser,
transformOrchestra
}
How should I restructure the code to prevent an infinite loop but keeping the transform functions as the final providers of data for a particular field ?

Return value after all the promises are resolved

I am working on a nodejs code that fetches data from a site, parses it, finds particular data and fetches something else for the data that was previously fetched. But the final return statement is returning without the value fetched from the second API call.
I tried to implement async await, but I am not sure where do I have to put them exactly.
const getMainData = async val => {
let result = [];
//get xml data from the API
const xmlData = await getSiteContent(`value`); //axios call
parseString(xmlData, (err, json) => { //convert xml to json
const { entry } = json.feed; // array of results.
result = entry.map(report => {
const secondInfo = getSomeMoreData(report.something); //axios call
const data = {
id: report.id,
date: report.date,
title: report.title
};
data.info = secondInfo;
return data;
});
});
return { result };
};
I was expecting the function to return the array result that has id, date, title and info. But I am getting info as null since it is going to another function that does one more API call.
Try wrapping parseString in a promise so you can await the result, then make the entry.map callback an async function so that you can use the await keyword to wait for the result of the axios fetch.
async function xml2json(xml) {
return new Promise((resolve, reject) => {
parseString(xml, function (err, json) {
if (err)
reject(err);
else
resolve(json);
});
});
}
const getMainData = async val => {
//get xml data from the API
const xmlData = await getSiteContent(`value`); //axios call
const json = await xml2json(xmlData);
const { entry } = json.feed; // array of results
const result = await Promise.all(
entry.map(async report => {
const secondInfo = await getSomeMoreData(report.something); // axios call
const data = {
id: report.id,
date: report.date,
title: report.title,
};
data.info = secondInfo;
return data;
})
)
return { result };
}
Let me know if that works. If not, I can try to help you out further.
The problem with your code is you have mixed promises concept(async/await is a syntactic sugar - so same thing) along with callback concept.
And the return statement is outside callback() of parseString() and the callback would be executed maybe after returning results only because parseString() is an asynchronous function.
So in the following solution I have wrapped parseString() in a promise so that it can be awaited.
const parseStringPromisified = async xmlData => {
return new Promise((resolve, reject) => {
parseString(xmlData, (err, json) => {
if (err) {
reject(err);
}
resolve(json);
});
});
};
const getMainData = async val => {
//get xml data from the API
const xmlData = await getSiteContent(`value`); //axios call
const json = await parseStringPromisified(xmlData);
const { entry } = json.feed; // array of results.
const result = entry.map(async report => {
const secondInfo = await getSomeMoreData(report.something); //axios call
return {
id: report.id,
date: report.date,
title: report.title,
info: secondInfo
};
});
return Promises.all(result);
};

Node js, Wait, until get all data from MongoDB

I have a problem with async function and callbacks in Node js. I need to get my friends' all posts and display it. But if i do that without setTimeout(), it returns only some part of data. How can i solve this problem without putting setTimeout? Of course it's absurd to wait for 5-6 or 10 seconds to get all data. I also tried with Promises, but again response is incomplete. Please someone can help me?
//Sending request with axios to Controller
axios.post(packages.proxy+'users/getFriendsPosts',{id: user_id},config)
.then(res => {
// Code for displaying result
})
//User Controller
router.post("/getFriendsPosts", getFriendsPosts);
//Send request body to userService.js
function getFriendsPosts(req, res, next) {
userService.getFriendsPosts(req.body, function(posts, user){
res.json({posts,user});
})
.catch(err => next(err));
}
//userService.js
module.exports = {
getFriendsPosts,
};
async function getFriendsPosts(user,callback){
var arr = [];
var array = [];
MongoClient.connect(url, async function(errr, db) {
if (errr) throw errr;
var dbo = db.db("drone-x");
//Find user
dbo.collection("users").find({_id: ObjectId(user.id)}).toArray(async function(err, result) {
if (err) throw err;
result.forEach(async function(element, index) {
if(element.friends.length != 0){
element.friends.forEach(async function(elem) {
//Find user's friends
dbo.collection("users").find({_id: ObjectId(elem.id)}).toArray(async function(error, res) {
if (error) throw error;
//push user's friends to arr
arr.push(res);
res.forEach(async function(elements) {
//Find user's friends posts
dbo.collection("posts").find({userId: elements._id.toString()}).toArray(async function(errors, results) {
if (errors) throw errors;
//push user's friends posts to array
array.push(results);
//callback results through setTimeout
setTimeout(async function(){ await callback(array, arr); db.close(); }, 2000);
});
});
});
});
}
else
{
await callback("0");
}
});
});
});
}
If i don't use setTimeout function, it just returns 2-3 data, but with setTimeout, it returns all data. And if data will be raise, then i need to increase the setTimeout time. But of course it's not good idea. Someone can help me?
You should use try catch in this code
getFriendsPosts = async (user,callback) => {
const arr = [];
const array = [];
const db = await MongoClient.connect(url);
const dbo = db.db("drone-x");
const results = await dbo.collection("users").find({_id: ObjectId(user.id)})
const resultPromise = _.map(results, async element => {
const friends = _.get(element, 'friends', [])
if(friends.length != 0) {
const friendPromise = _.map(friends, async friend => {
const ress = await dbo.collection("users").find({_id: ObjectId(friend.id)})
arr.push(ress);
const resPromise = _.map(ress, async res => {
const posts = await dbo.collection("posts").find({userId: res._id.toString()})
const postPromise = _.map(posts, post => {
array.push(post);
})
await Promise.all(postPromise)
})
await Promise.all(resPromise)
})
await Promise.all(friendPromise)
}
})
await Promise.all(resultPromise)
return { arr , array }
}
I am not recommand this way it take too much time. You should use mongoose and use aggregation for long Query.

Koa.js always get Not Found 404

I develop in Koa and I use Firebase to messaging, because of a real-time database. When I want to get messages from firebase I get Not found, but in console.log() it shows me.
This is my function to getConversation(Messages)
async getConversation(conversationName, callback) {
var ref = await admin.database().ref(`messages/${conversationName}`)
await ref.on('value', (snapshot, prevChildKey) => {
var newPost = snapshot.val()
let values = Object.values(newPost)
callback(values)
})
}
Then I call it in another controller like this
async getMessages(ctx) {
const id = ctx.params.id
const nameOfConversation = await ctx.db.Conversation.findById(id)
await firebaseIndex.fbController.getConversation(nameOfConversation.name, response => {
console.log(response)
ctx.body = response //TODO
})
}
At the last, I call it in routes.
router.get('/getConversation/:id', middlewares.isAuthenticate, controllers.userConversation.getMessages)
I always get body Not found.
Do anybody know how I can solve it?
I solved it.
async getMessages(ctx) {
const id = ctx.params.id
const nameOfConversation = await ctx.db.Conversation.findById(id)
ctx.body = await new Promise((resolve, reject) => {
firebaseIndex.fbController.getConversation(nameOfConversation.name, async response => {
resolve(response)
})
})
}
ctx.body has to have a Promise.

Resources