So, I am creating an API in NodeJS. In one API, I have to call for loop within mongoose query.
How do I wait for the forEach to complete before executing res.send()? I have attached my code below.
router.post("/getResult", function (req, res) {
const lottery_id = req.body.lottery_id;
const prizeQuery = Prize.find({"lotid": lottery_id});
let response = [];
prizeQuery.exec(function (err, prizes) {
console.log(prizes.length);
if (err) return res.send({success: 0, data: err});
else {
prizes.forEach(prize => {
let prizeData = {};
const winnerQuery = Winner.find({"id": prize._id});
winnerQuery.exec(function (err, winners) {
if (err) return res.send({success: 0, data: err});
else {
prizeData = {
prize: prize,
winners: winners
};
response.push(prizeData);
}
});
});
}
});
return res.send({success:1, data: response});
});
In the code above, return is called before forEach is completed.
Because you are running asynchronous code inside the forEach and forEach will not wait until the asynchronous code finish, to do so, you must wrap your async code with a waiting primitive.
Also the code you provided will call send twice in case of failure, because the return inside the forEach will not actually end the enclosing function.
try {
await Promise.all(prizes.map(async (prize) => {
const winners = await Winner.find({"id": prize._id});
response.push({prize, winners});
}))
res.send({success:1, data: response});
} catch (err) {
res.send({success: 0, data: err});
}
#Rami's answer is correct but addition to that you can also use forof for your code to work. forof works synchronously so there will be no need for promise. like
for (let prize of prizes){
let prizeData = {};
try{
const winnerQuery = Winner.find({"id": prize._id});
const winners = await winnerQuery.exec()
prizeData = {
prize: prize,
winners: winners
};
response.push(prizeData);
}catch(err){
console.error(err)
}
}
Related
In my node.js express server i have the api function in which i am doing async operations for-of loop.
controller.js
const importRecords = async(req,res) => {
for (const bid of distinctArr) {
// doing 3 async operations for each loop iteration with `await`
let transaction = await db.sequelize.transaction();
let bidCreate = await createBid(bid.bid, transaction)
if(bidCreate){
let usersCreate = await createUsers(bid.users, transaction);
let ordersCreate = await ordersCreate(bid.orders, transaction)
await transaction.commit();
}
}
return{
status: true,
message: "Records imported successfully",
response: null,
}
}
handler.js
exports.ImportBookings = async(req, res) => {
try {
const result = await importNewBookings(req);
res.json(result);
} catch (error) {
res.status(error.code || 500).send({
code: error.code,
message: error.message,
response: null
})
}
}
routes.js
router.post('/import-bookings', ImportBookings)
You can see that i am returning the response after loop finishes but the problem is that all the operations/db entries are doing well but response is not returning to the api. How can i do that?
Note: I just included the necessary code to just show problem statement
You may favour the use of a lock in asynchronous JS. I guess your distinctArr is generator coz it's used with of keyword, change the distinctArr.length to something equivalent (for example: Array.from(distinctArr).length, [...distinctArr].length).
function newLock(){
var unlock,lock=new Promise((res,rej)=>{ unlock=res; });
return [lock,unlock];
}
const importRecords = async(req,res) => {
// *** CREATE LOCK ***
var [lock,unlock] = newLock();
var doneCount = 0;
for (const bid of distinctArr) {
// doing 3 async operations for each loop iteration with `await`
let transaction = await db.sequelize.transaction();
let bidCreate = await createBid(bid.bid, transaction)
if (bidCreate){
let usersCreate = await createUsers(bid.users, transaction);
let ordersCreate = await ordersCreate(bid.orders, transaction)
await transaction.commit();
}
// *** COUNT TRANSACTIONS DONE ***
doneCount++;
if (doneCount>=distinctArr.length)
unlock();
}
// *** AWAIT THE LOOP TO BE DONE ***
await lock;
res.json({
status: true,
message: "Records imported successfully",
response: null,
});
}
So finally i resolved my problem. I simply just wrap-up my whole working inside of Promise and then i put my returning object to promise's resolve so that it resolves the promise value when loop finishes. Here is my updated code of function which is now working fine.
controller.js
const importRecords = async(req,res) => {
return new Promise((resolve, reject) => {
for (const bid of distinctArr) {
// doing 3 async operations for each loop iteration with `await`
let transaction = await db.sequelize.transaction();
let bidCreate = await createBid(bid.bid, transaction)
if(bidCreate){
let usersCreate = await createUsers(bid.users, transaction);
let ordersCreate = await ordersCreate(bid.orders, transaction)
await transaction.commit();
}
}
resolve({
status: true,
message: "Records imported successfully",
response: null,
})
})
}
I am trying to search available objects of a MongoDB collection(e.g. ParkingSpot) using specific business logic, the problem is that inside this function which is an async/await func I am looking inside another collection with .find() which I use as an async/await as well. The second function doesn't execute and gets back as an promise " Promise { } " and therefore the business logic is not applied. Can you help me please?
Here is 1st function:
exports.get_parkingSpots_avail_for_parking = async (req, res, next) => {
try {
const radius = req.body.radius;
const userLatitude = req.body.userLatitude;
const userLongitude = req.body.userLongitude;
const reqStartDate = new Date(req.body.reqStartDate);
const reqEndDate = new Date(req.body.reqEndDate);
const parkingSpots = await ParkingSpot.find({
isAvailable: true
}, (err, parkingSpots) => {
if (err) {
return res.status(500).json({
error: err
});
}
const freeParkingSpots = parkingSpots.filter(parkingSpot => {
return distanceCalculator(parkingSpot.latitude, parkingSpot.longitude, userLatitude, userLongitude) < radius
})
const availParkingSpots = freeParkingSpots.filter( freeParkingSpot => {
// console.log(freeParkingSpot._id);
console.log(isParkingAvailable(reqStartDate, reqEndDate, freeParkingSpot._id));
return isParkingAvailable(reqStartDate, reqEndDate, freeParkingSpot._id);
})
}).select('_id userId number address latitude longitude notes isAvailable startOfAvailability endOfAvailability');
console.log(parkingSpots);
if (parkingSpots.length > 0) {
return res.status(200).json({
parkingSpots: parkingSpots
});
}
return res.status(404).json({
message: "No available parking spots for requeste period"
});
} catch (err) {
res.status(500).json({
error: err
})
}
};
Second function which is being called :
module.exports = async function isParkingAvailable(reqStartDate, reqEndDate, parkingSpotId) {
const parkingSpotBookings = await Booking.find({ parkingSpotId: parkingSpotId})
.select('_id parkingSpotId sharerUserId renterUserId startDate endDate');
if (parkingSpotBookings.length == 0) {
return true;
}
parkingSpotBookings.filter(booking => {
//console.log(parkingSpotId);
//console.log("Is overlapping" +!isOverlapping(reqStartDate, reqEndDate, booking.startDate, booking.endDate));
return !isOverlapping(reqStartDate, reqEndDate, booking.startDate, booking.endDate)
})
}
So the problem is that calling second function appears as that in console.log :Promise { }
Await will Return the result to your parkingSpot variable.
For the second function:
You have defined this function as an async, that means it is holding asynchronous process, and in Node JS es6 async process is processed as a promise, so if you won't call it with await it will return a promise only
exports.get_parkingSpots_avail_for_parking = async (req, res, next) => {
try {
const radius = req.body.radius;
const userLatitude = req.body.userLatitude;
const userLongitude = req.body.userLongitude;
const reqStartDate = new Date(req.body.reqStartDate);
const reqEndDate = new Date(req.body.reqEndDate);
const parkingSpots = await ParkingSpot.find({isAvailable: true}).select('_id userId number address latitude longitude notes isAvailable startOfAvailability endOfAvailability');
const freeParkingSpots = parkingSpots.filter(parkingSpot => {
return distanceCalculator(parkingSpot.latitude, parkingSpot.longitude, userLatitude, userLongitude) < radius
});
const availParkingSpots = freeParkingSpots.filter( async freeParkingSpot => {
/* You have defined this function as an async, that means it is holding asynchronous process, and in
Node JS es6 async process is processed as a promise, so if you won't call it with await it will return a promise only */
return await isParkingAvailable(reqStartDate, reqEndDate, freeParkingSpot._id);
});
if (parkingSpots.length > 0) {
return res.status(200).json({
parkingSpots: parkingSpots
});
}
return res.status(404).json({
message: "No available parking spots for requeste period"
});
} catch (err) {
res.status(500).json({
error: err
})
}
};
I hope it will help you.
Thank you
I have been having a problem waiting for one part of my code to finish for me to run the other part,
How can part 1 finish before part 2?
var uidArray = [];
// Part 1
admin.auth().listUsers(1000).then(result => {
result.users.forEach(userRecord => {
uidArray.push(userRecord.uid);
console.log("pushing to uid array user " + userRecord.uid);
});
return;
}).catch(error => {
console.log("Error listing users:" + error);
});
uidArray.forEach(uid => {
// Part 2 of the code
});
You can use async-await for better data manipulation. However, forEach is by default asynchronous it can't wait until for each completes you can use below using async-await with try-catch block.
try{
var uidArray = [];
const result = await admin.auth().listUsers(1000);
for(const userRecord of result.users) {
uidArray.push(userRecord.uid);
console.log("pushing to uid array user " + userRecord.uid);
}
uidArray.forEach(uid => {
// Part 2 of the code
});
} catch(err) {
console.log(err);
}
var uidArray = [];
// Part 1
admin.auth().listUsers(1000).then(result => {
result.users.forEach(userRecord => {
uidArray.push(userRecord.uid);
console.log("pushing to uid array user " + userRecord.uid);
});
return;
}).catch(error => {
console.log("Error listing users:" + error);
}).then(()=>{
uidArray.forEach(uid => {
// Part 2 of the code
});
});
The key here is that after you start a promise, you can keep thening off it. Ideally you should also return a promise after each step, but it's not required.
This link has more information
https://javascript.info/promise-chaining
You can use below code using async-await as it is good for data manipulation.i prefer for loop instead using forEach.
async function test(){
try{
let result = await admin.auth().listUsers(1000);
let usersArray = result.users;
var uidArray = usersArray.map(function(item) { return item.uid; });
// here you can loop through uidArrar either for or forEach whatever you want
for(let i =0 ; i< uidArray.length; i++){
// your code to be executed in second part
}
} catch (e) {
//error
}
}
test()
I have the method:
const getTaskOrCategoryRecursive = async (type, id)=>{
const rslt = await type.findOne.call(type, {_id:id},{}, standardOptions, err=>{
if(err){
return({err: err});
}
});
const result = rslt.toObject();
const tasks = result.tasks;
const events = result.events;
result.children = {};
result.children.tasks = tasks.map(async taskId=>{
const taskIdString = taskId.toString();
const rtrn = await getTaskRecursive(taskIdString, err=>{
if(err){
return({err: err});
}
});
return rtrn;
});
result.children.events = events.map(async eventId=>{
const eventIdString = eventId.toString();
await Event.findOne({_id:eventIdString}, standardOptions,err=>{
if(err){
return({err: err});
}
});
});
return result;
}
It's called by two methods:
const getTaskRecursive = (taskId)=>{
return getTaskOrCategoryRecursive(Task, taskId)
}
and
const getCategoryRecursive=(categoryId)=>{
return getTaskOrCategoryRecursive(Category,categoryId);
};
which are called by the function
router.get('/:id', verifyToken, async (req,res)=>{
Branch.getCategoryRecursive(req.params.id)
.then(
(cat)=>{
res.status(200).send(cat);
},
(err)=>{
res.status(500).send(err);
}
)
});
When I try to run the program, first the getCategoryRecursive method runs, then res.status(200).send(cat); then the getTasksRecursive method which gets the children of the object being sent in the response. getTasksRecursive does what it is supposed to, but it's running after the response is sent so the object is empty.
How do I make my asynchronous method run before the response is sent?
UPDATE: Using Aritra Chakraborty's answer, I changed it to the following and it worked.
First I separated the .map into a new function:
const getAllTasksRecursive = async(taskIds)=>{
const rtrn = await Promise.all(
taskIds.map(
async taskId=>{
return await getTaskRecursive(taskId.toString(), err=>{if(err){return {err:err}}});
}
)
)
return rtrn;
}
Then I called it in the previous function using:
result.children.tasks = await getAllTasksRecursive(tasks);
Now I am getting the data back in the response.
That's because internally a map or foreach can look something like this:
Array.prototype.forEach = function (callback) {
// this represents our array
for (let index = 0; index < this.length; index++) {
// We call the callback for each entry
callback(this[index], index, this)
}
}
It will call the callback alright, but it will not wait for the callback to complete before running the next one.
So, you need one async foreach of some sort,
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
Note, how the callback is being awaited.
Your code will have to be something like this:
const start = async () => {
await asyncForEach([1, 2, 3], async (num) => {
await waitFor(50)
console.log(num)
})
console.log('Done')
}
start()
Refer: https://codeburst.io/javascript-async-await-with-foreach-b6ba62bbf404 This article helped me alot in the past.
The problem is with the promises and the async function. "All moved" is supposed to be logged after everything in async.each is done. But nothing is ever logged.
Here is my exports functions:
var courier_id = data.ref.parent.key;
return admin.database().ref("firewall_queue/"+courier_id+"/orders").once('value',function(orders){
//console.log(Object.keys(orders.val()));
async.each(Object.keys(orders.val()), function (order, callback) {
if(order != "none") {
return moveToWaitingFromFirewall(order).then(callback())
}
},
function (err) {
console.log("All moved");
return admin.database().ref("/firewall_queue/"+courier_id+"/orders/").remove().then(()=>{
return pushToPending(courier_id,data.ref.key);
})
});
})
Here is my moveToWaitingFromFirewall function:
function moveToWaitingFromFirewall(order_id){
var order = {};
order.id = order_id;
var promises = [];
promises.push(new Promise((resolve) => {
admin.database().ref("orders/"+order_id+"/zone").once('value').then(function(zone){
order.zone = zone.val();
resolve();
})
}))
promises.push(new Promise((resolve) => {
admin.database().ref("orders/"+order_id+"/time_order_placed").once('value').then(function(time_order_placed){
order.time = time_order_placed.val();
resolve();
})
}))
//grab zone and time first
return Promise.all(promises).then(()=>{
return admin.database().ref(order.zone+"/wait_order_queue/"+order.id).set(order.time);
})
}
JSON Firebase
"c98" : {
"orders" : {
"0333" : 123123,
"0345" : 12,
"0911" : 123,
"none" : "none"
}
Study this a little bit, and maybe apply to your current code.
Imagine admin.database().ref("orders/"+order_id+"/time_order_placed").once('value') is like delay(time)
// let delay = time => new Promise(res=>setTimeout(res,time));
let delay = function(time){
return new Promise(function(resolve,reject){
setTimeout(function(){
resolve();
},time);
});
}
let myPromise = function(order){
return Promise.all([
delay(500),
delay(500),
delay(1000).then(function(){
console.log('Order complete: ',order);
return; // returns undefined, so cb doesn't pass anything to cb(err), but use ()=>cb() to avoid this anyways.
})
]);
}
let orders = [1,2,3];
async.each(orders,function(order,cb){
myPromise(order)
.then(()=>cb())
.catch(err=>cb(err));
},function(err,data){
if(err){
console.log('Err',err);
}else{
console.log('all Finished');
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/async/2.6.0/async.js"></script>