Async reduce returning promises - javascript

I have an array of objects and I have to add one property on each of the objects coming from and async function
I am doing an Array.reduce to iterate on each of the elements and return just one result: One array of objects with the new property.
I have this
const res = await resultOne.reduce(async (users = [], user, i) => {
let userName;
try {
let { name } = await names.getNames(user.id);
userName = name;
} catch (error) {
throw error;
}
delete user.id;
users.push({ ...user, userName });
return users;
}, []);
But I get the message
Push is not a function of users
And this is because I think is a promise.
How can I handle async requests in a reduce or a map

Yes, users is a promise. Don't use reduce with async functions. You could do something like await users in the first line, but that would be pretty inefficient and unidiomatic.
Use a plain loop:
const users = [];
for (const user of resultOne) {
const { name } = await names.getNames(user.id);
delete user.id;
users.push({ ...user, userName: user });
}
or, in your case where you can do everything concurrently and create an array anyway, the map function together with Promise.all:
const users = await Promise.all(resultOne.map(async user => {
const { name } = await names.getNames(user.id);
delete user.id;
return { ...user, userName: user };
}));

Because it's an async function, every time you return something it gets wrapped in a promise. To fix this you need to set the starting array as a promise and then await for the accumulator on each iteration.
const res = await resultOne.reduce(async (users, user, i) => {
try {
return [
...await users,
{ ...user, userName: await names.getNames(user.id.name) }
]
} catch (error) {
console.log(error)
}
}, Promise.resolve([]));

Related

JavaScript - Async/Await - How to Return result from nested functions?

I'm new to Node.js and having difficulty working with async/await model. The issue I'm having is with nested function calls returning data in an async/await method, I always get 'undefined'. My functions structure is like this:
const findCustomerOrder = async (id) => {
const orders = await Order.find({custoemrId:id})
.then(async (order) => {
if(order)
{
const products = await findProductsByOrderId(order._id)
.then(async (result) => {
for(const r in result){
console.log("Product ID: ", result._id);
}
});
}
else
console.log("order not found");
});
}
const findProductsByOrderId = async (id) => {
try{
const products = await Products.find({orderId:id}, (error, data) => {
if(error) console.log(error);
else
return data;
});
}
catch(err){
return 'error!!';
}
}
I understand that if the top-level call is async/await then all the nested calls should be awaited as well that I've tried to do.
What am I doing wrong here?
Get rid of all the then, and also of the callback in the DB query. Use only await. It will make the whole code a lot easier to reason about and it should also solve your issue as part of the restructuring.
In the example below I also added a loop over the orders, because you are fetching all orders, as array, but then your code was behaving as if it got only one. I also fixed the products loop.
However, the way you fetch products doesn't seem to be right anyway, see my comment below. I didn't fix it because I don't know how your database structure looks, so I don't know what the right way would be.
async function findCustomerOrder (id) {
const orders = await Order.find({ customerId: id })
for (const order of orders) {
console.log(`Order ID: ${order._id}`)
const products = await findProductsByOrderId(order._id)
for (const product of products) {
console.log(`Product ID: ${product._id}`);
}
}
}
async function findProductsByOrderId (id) {
// Note: I don't think this is right. It seems to find all
// products with the given ID - which will always be one -
// and that ID would be the _product_ ID and not the order ID.
return await Products.find({ _id: id })
}
Preemptive comment reply: The return await is intentional, and this is why.
You should remove the then and also the callback and you can do something like below
const findCustomerOrder = async(id) => {
try {
const orders = await Order.find({
custoemrId: id
});
if (orders) {
let promiseArray = [];
orders.forEach(order => {
promiseArray.push(findProductsByOrderId(order._id));
});
let results = await Promise.all(promiseArray);
return results;
}
return "order not found";
} catch (err) {
throw err;
}
}
const findProductsByOrderId = async(id) => {
try {
const products = await Products.find({
_id: id
});
return products;
} catch (err) {
throw err;
}
}

How can I update mysql2 query to not return undefined for return?

I am using nodejs and mysql2. I am storing all my queries inside a class. When I console.log the results I am able to view the data correctly, however when I use a return statment to return the data, I am getting undefined. I believe I need to use promises, but am uncertain how to do so correctly. Here is what I have currently which is returning undefined,
viewManagerChoices() {
const sql = `SELECT CONCAT(first_name, ' ', last_name) AS manager, id FROM employee WHERE manager_id IS NULL`;
db.query(sql, (err, rows) => {
if (err) throw err;
const managers = rows.map(manager => ({ name: manager.manager, value: manager.id }));
managers.push({ name: 'None', value: null });
return managers;
});
};
This is my attempt at using promises which is returning as Promise {<pending>},
viewManagers() {
return new Promise((resolve, reject) => {
const sql = `SELECT CONCAT(first_name, ' ', last_name) AS manager FROM employee WHERE manager_id IS NULL`;
db.query(sql,
(error, results) => {
if (error) {
console.log('error', error);
reject(error);
}
const managers = [];
for (let i = 0; i < results.length; i++) {
managers.push({ name: results[i].manager, value: i+1 });
}
managers.push({ name: "None", value: null });
resolve(managers);
}
)
})
}
My class is called Query and I am calling these methods by doing,
const query = new Query();
query.viewManagerChoices();
query.viewManagers();
Your implementation for viewManagers is correct however promises don't make calls synchronous.
Either you need to use then callback or await the result in async context.
const query = new Query();
query.viewManagers().then((managers) => {
// do stuff here
}).catch((error) => console.error(error.message));
or
async someFunc() {
const query = new Query();
try{
const managers = await query.viewManagers();
}catch(error){
console.error(error.message);
}
}
Once you use promise you cannot just get a returned value without async/await or then flag. Once it's a promise the flow continues as the original.
For example:
// This is promise too because it has async flag.
// You cannot make it sync if you use promise in it
async myFunc(){
const const query = new Query();
const managers = await query.viewManagers();
return managers;
}
// It actually returns Promise<...>
// Now if you want to use myFunc in another function
// You need to do it the same way again
async anotherFunc(){
const something = await myFunc();
return something; // Returns promise
}
You can read more about promises here

Promise trouble! How can I write this in such a way that it waits for the end?

Trying to write this in a way that it will wait for the db operations to complete. Possible?
function addFriendsToSession(session, sessionId) {
const friends = [];
const incoming = session.users.forEach(async user => {
console.log('user', user);
await db
.collection('users/' + user + '/settings')
.doc(user)
.get()
.then(doc => {
if (doc.exists) {
const returnObj = doc.data();
return returnObj.friends ? returnObj.friends : [];
} else {
return [];
}
});
});
friends.push(incoming);
return friends;
}
Use Promise.all.
Promise.all accepts an array of promises, and resolves once each promise has resolved. You can map your operation using map. E.g.,
const promises = session.users.map(user => {
console.log('user', user);
return db
.collection('users/' + user + '/settings')
.doc(user)
.get()
.then(doc => {
if (doc.exists) {
const returnObj = doc.data();
return returnObj.friends ? returnObj.friends : [];
} else {
return [];
}
});
});
const friends = await Promise.all(promises)
return friends;
There are a number of issues here.
In db.then(), return is used, but this value is never returned from the function that encloses it (which is async user => {...})
const incoming is assigned the result of session.users.forEach, but Array.forEach() never has a return value (you may be thinking of Array.map()?)
Even if you solved the first two problems, incoming would still be an array of Promises
Additional suggestions:
Don't mix async/await with .then
Putting it all together:
const incoming = session.users.map(async user => {
console.log('user', user);
const doc = await db
.collection('users/' + user + '/settings')
.doc(user)
.get();
//we assigned `doc` using await instead of using .then
if (doc.exists) {
const returnObj = doc.data();
return returnObj.friends ? returnObj.friends : [];
} else {
return [];
}
});
//incoming is not an array of Promises
const incomingFriends = await Promise.all(incoming); //takes an array of Promises and returns an array of the resolved values
//incomingFriends is now an array of friends
//next line would make the last element an array
//friends.push(incoming);
//you probably want to push every element of the array
friends.push(...incomingFriends);

How to handle this javascript asynchronous function

I have this function in javascript node
exports.getUserEmailByAccess = async (moduleType) => {
let emailList = []
const foundUsers = await Access.find({moduleType:moduleType})
console.log(foundUsers);
await foundUsers.forEach(access => {
User.findById(access.userId)
.then(foundUser => {
console.log(foundUser.workerEmail);
emailList.push(foundUser.workerEmail)
})
});
console.log(emailList);
return emailList
}
What I want is to push into emailList array by looping object array, the above approach results in an empty array,so I tried a different following way
exports.getUserEmailByAccess = async (moduleType) => {
let emailList = []
const foundUsers = await Access.find({moduleType:moduleType})
console.log(foundUsers);
await foundUsers.forEach(access => {
const foundUser = User.findById(access.userId)
console.log(foundUser.workerEmail);
emailList.push(foundUser.workerEmail)
});
console.log(emailList);
return emailList
}
By doing this , the array list is getting filled but with an [undefined]strong text value, I come from a humble python control structure background, Please can I know why I am not able to push data into array even after using async/await
If the User.findById() returns a promise, I'd recommend using Promise.all() instead of individually running all promises using forEach to fetch documents of all foundUsers:
exports.getUserEmailByAccess = async (moduleType) => {
const foundUsers = await Access.find({ moduleType: moduleType });
console.log(foundUsers);
const userDocs = await Promise.all(
foundUsers.map((user) => User.findById(user.userId))
);
const emailList = userDocs.map((user) => user.workerEmail);
// or alternatively
// const emailList = userDocs.map(({workerEmail}) => workerEmail);
console.log(emailList);
return emailList;
};
you can give try this
exports.getUserEmailByAccess = async (moduleType) => {
let emailList = []
const foundUsers = await Access.find({ moduleType: moduleType })
console.log(foundUsers);
await foundUsers.map(async access => {
let result = await User.findById(access.userId);
if (result) {
emailList.push(foundUser.workerEmail)
}
});
console.log(emailList);
return emailList
}
UPDATED
await Promise.all(
foundUsers.map(async access => {
let result = await User.findById(access.userId);
if (result) {
emailList.push(foundUser.workerEmail)
}
})
])
console.log(emailList);
return emailList
How are your Models related? I think you might do a populate here if the Access Model has the ObjectId of the User Model. Something like this:
const foundUsers = await Access.find({ moduleType: moduleType }).populate({
path: 'users', // the name of the field which contains the userId
select: '-__v' // fields to bring from database. Can add a (-) to NOT bring that field
})
The idea is that you specify all the fields that you need. Then when you receive the data, you can do something like:
foundUsers.users.email

ExpressJs wait till MongoDB fetch data and loop through before the output

I'm having trouble in figuring this to work, I have one table from MongoDB (collection) for comments and another collection for Users.
When the page load it looks up the comment collection and selects the relevant comments, and then it searches the user table to find the name of the user who made the comment, the data will be combined and then the response is sent.
However, the output is sent before the data is fetched from the user table and added. How can I fix this, here is my code
var output = []
const Comments = require('comments.js')
const Users = require('users.js')
function delay( ) {
return new Promise(resolve => setTimeout(resolve, 300))
}
async function delayedProcess(item) {
await delay()
Users.findById(item.user, async function(err, result) {
Object.assign(item, {name: result.name})
output.push(item)
})
}
async function processArray(array) {
const promises = array.map(delayedProcess)
await Promise.all(promises)
return res.json({data: output})
}
Comments.find({page_id: id}).sort({post_date:-1}).limit(6).then(function(data) {
processArray(data)
})
You are not returning promise from the delayedProcess function.
There you go :-
const Comments = require('comments.js')
const Users = require('users.js')
const output = []
function delayedProcess(item) {
return new Promise((resolve, reject) => {
Users.findById(item.user, function(err, result) {
if (err) return reject (err);
output.push({ ...item, name: result.name })
return resolve()
})
})
}
async function processArray(array) {
const promises = array.map(async(item) => {
await delayedProcess(item)
})
await Promise.all(promises)
return res.json({ data: output })
}
const data = await Comments.find({ page_id: id }).sort({ post_date: -1 }).limit(6)
processArray(data)
However you will always get the concatenated array. So instead taking it globally, take it as local variable
function delayedProcess(item) {
return new Promise((resolve, reject) => {
Users.findById(item.user, function(err, result) {
if (err) return reject (err);
return resolve({ ...item, name: result.name })
})
})
}
async function processArray(array) {
const output = []
const promises = array.map(async(item) => {
const it = await delayedProcess(item)
output.push(it)
})
await Promise.all(promises)
return res.json({ data: output })
}
const data = await Comments.find({ page_id: id }).sort({ post_date: -1 }).limit(6)
processArray(data)
More simplified :- Since mongodb queries itself returns promise you do not need to use new Promise syntax.
async function processArray() {
const array = await Comments.find({ page_id: id }).sort({ post_date: -1 }).limit(6)
const output = []
const promises = array.map(async(item) => {
const it = await Users.findById(item.user).lean()
item.name = it.name
output.push(item)
})
await Promise.all(promises)
return res.json({ data: output })
}

Categories