Call synchronously recursively function in nodejs - node.js

I'm developing MEANJS project. Here a function which return array by fetch data recursively. Now, I have the problem that how to get that array.
Problem:-
let users A have properties is {_id:'001',rmUserId:'001'}, B:{_id:'002',rmUserId:'001'}, C {_id:'003',rmUserId:'002'}, D {_id:'003',rmUserId:'001'}, E {_id:'004',rmUserId:'009'}
if user A will login then allUnderUsers array have B,C,D users. That means all users have to follow an own hierarchy.
A
/ \
B D
/
C
Here is my code:-
module.exports.getUnderUsersByRm = function(currentUser, callback) {
try {
var allUnderUsers = [];
function __getUserByRmId(rmId) {
User.find({ rmUserId: rmId, isAlive: true, status: 'active' })
.exec(function(err, users) {
if (err)
return callback(err)
if (users.length > 0) {
users.forEach(function(ele, i) {
allUnderUsers.push(ele);
__getUserByRmId(ele.rmUserId);
});
} else {
return false;
}
})
}
__getUserByRmId(currentUser._id);
} catch (e) {
callback(e)
}
}
Here I need to get allUnderUsers array after all recursive function called.
I have use callback function like:-
....
...
__getUserByRmId(currentUser._id);
callback(null,'done');
.
.
but it throws an error i.e,
Error: Can't set headers after they are sent
.at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:346:11)
at ServerResponse.header (/home/clsah/projects/LMS/node_modules/express/lib/response.js:719:10)
........ .......

If you take advantage of the promises built into the later versions of mongoose and surface a promise interface from your module, you can do this:
Simulated running code here: https://jsfiddle.net/jfriend00/zr6ynmsu/
module.exports.getUnderUsersByRm = function(currentUser) {
function __getUserByRmId(rmId) {
// return promise here
return User.find({ rmUserId: rmId, isAlive: true, status: 'active' }).exec().then(function(users) {
if (users.length > 0) {
let promises = [];
users.forEach(function(ele, i) {
promises.push(__getUserByRmId(ele.rmUserId));
});
// return promise which will chain it to original promise
// this is the key to getting the master promise to wait
// for everything to be done
return Promise.all(promises).then(results => {
// add in previous results
// flatten all the results together into a single array
// and remove empty results
results.unshift(users);
return [].concat.apply([], results.filter(item => item.length > 0));
});
} else {
return [];
}
});
}
return __getUserByRmId(currentUser);
}
And, then you would use it like this:
const someModule = require('yourModule');
someModule.getUnderUsersByRm(someUser).then(results => {
// process all results here
}).catch(err => {
// error here
});
If you still want your callback interface on getUnderUsersByRm, you can still do that (though if you're doing more than a few async calls, it really is worth using promises for all async operations):
module.exports.getUnderUsersByRm = function(currentUser, callback) {
function __getUserByRmId(rmId) {
// return promise here
return User.find({ rmUserId: rmId, isAlive: true, status: 'active' }).exec().then(function(users) {
if (users.length > 0) {
let promises = [];
users.forEach(function(ele, i) {
promises.push(__getUserByRmId(ele.rmUserId));
});
// return promise which will chain it to original promise
// this is the key to getting the master promise to wait
// for everything to be done
return Promise.all(promises).then(results => {
// add in previous results
// flatten all the results together into a single array
// and remove empty results
results.unshift(users);
return [].concat.apply([], results.filter(item => item.length > 0));
});
} else {
return [];
}
});
}
__getUserByRmId(currentUser).then(result => {
callback(null, result);
}).catch(err => {
callback(err);
});
}
If your user tree is circular, then you can protect against an infinite loop by keeping track of all the visited users. You need some sort of unique key that identifies each user. Since I don't know what that is in your program, I will assume the user you are passing in is already an id. Any property that uniquely identifies the user will work in this scheme:
module.exports.getUnderUsersByRm = function(currentUser) {
let visitedUsers = new Set();
function __getUserByRmId(rmId) {
// return promise here
return User.find({ rmUserId: rmId, isAlive: true, status: 'active' }).exec().then(function(users) {
if (users.length > 0) {
let promises = [];
users.forEach(function(ele, i) {
// make sure we aren't already processing this user
// avoid circular loop
let userId = ele.rmUserId;
if (!visitedUsers.has(userId)) {
visitedUsers.add(userId);
promises.push(__getUserByRmId(userId));
}
});
// return promise which will chain it to original promise
// this is the key to getting the master promise to wait
// for everything to be done
return Promise.all(promises).then(results => {
// add in previous results
// flatten all the results together into a single array
// and remove empty results
results.unshift(users);
return [].concat.apply([], results.filter(item => item.length > 0));
});
} else {
return [];
}
});
}
return __getUserByRmId(currentUser);
}

I tried to implement the solution using async based approach. You may find it naive, but it should work.
function __getUserByRmId(rmId, cb) {
var allUnderUsers = [];
User.find({ rmUserId: rmId, isAlive: true, status: 'active' })
.exec(function(err, users) {
async.each(users, function(user, callback){
if (user._id != rmId){
// recursive call
__getUserByRmId(user._id, function(childUsers){
childUsers.forEach(function (childUser) {
allUnderUsers.push(childUser);
});
callback(); //intermediate callback for async call
});
} else { //condition check to avoid infinite loop
allUnderUsers.push(user);
callback(); //intermediate callback for-loop
}
}, function(err){
cb(allUnderUsers); //final callback with result
});
});
}
module.exports.getUnderUsersByRm = function(currentUser, callback) {
__getUserByRmId(currentUser._id, callback)
};
Logically, it should work. Please give a try and let me know, if there are any issue. For now, it returns array containing parent as well. e.g. [A, B, C, D] for your example.

Use async.until and maintain an array of element to be processed
var async = require('async');
module.exports.getUnderUsersByRm = function(currentUser, callback) {
try {
var allUnderUsers = [];
var usersToProcess = [currentUser._id]; // Array to track what was earlier done with recursion
async.until(function() { // Test function for async.until
return usersToProcess.length === 0;
}, function __getUserByRmId(callback2) { // fn for async.until
User.find({
rmUserId: usersToProcess.shift(), // Take first element of array
isAlive: true,
status: 'active'
})
.exec(function(err, users) {
if (err)
return callback2(err)
if (users.length > 0) {
users.forEach(function(ele, i) {
allUnderUsers.push(ele);
usersToProcess.push(ele.rmUserId);
// __getUserByRmId(ele.rmUserId); // To Remove
});
return callback2(); // Always call callback;
} else {
return callback2(); // Earlier: return false; Return some err argument if you want
}
})
}, callback); // Final callback for async.until
} catch (e) {
callback(e);
}
}

I'm pretty sure that you are getting that error because you run __getUserByRmId function (which inside calls the callback function if an error occurs, and then, besides anything that happen you call the callback function again, which may be sending multiple responses to one same request, and so on, setting headers multiple times even when the response was already sent.
I should have post this in the comments but don't have enough reputation to do so

Related

Trying to use async.parallelLimit to make to make successive parallel API calls

I have a data array of objects like [{number:1}, {number:2}, {number:3}... {number:100}]. And want to make parallel API calls in successive batches of 10 until the whole array has been processed.
How would I go about that?
Here's my code. It goes over the first 10, but then it stops.
const async = require("async");
const axios = require("axios");
const calls = require("../model/data"); // [{number:1}, {number:2},{number:3},...{number:100}]
let makeAPICall = function (request, callback) {
axios
.post("http://www.api.com/", {
number: `${request.number}`,
webhookURL: "http://localhost:8000/",
})
.then(function (response) {})
.catch(function (err) {
callback(err);
});
};
const functionArray = calls.map((request) => {
return (callback) => makeAPICall(request, callback);
});
exports.startCalls = (req, res, next) => {
async.parallelLimit(functionArray, 10, (err, results) => {
if (err) {
console.error("Error: ", err);
} else {
console.log("Results: ", results.length, results);
}
});
};
So, your approach is somewhat backwards as you have something that already returns a promise (Axios) which is the modern way to manage asynchronous operations and now you're trying to convert it back to a plain callback so you can use an old-fashioned library (the async library). It's also slower to run 10, wait for all 10 to finish before starting any more when that is typically not necessary.
Instead, I would suggest you use the Axios promise you already have. You can either use Bluebird's .map() which has a concurrency setting or you can use this bit of code that gives you a function for running promise-producing functions in parallel while controlling the max number that are in-flight at any given time:
// takes an array of items and a function that returns a promise
function mapConcurrent(items, maxConcurrent, fn) {
let index = 0;
let inFlightCntr = 0;
let doneCntr = 0;
let results = new Array(items.length);
let stop = false;
return new Promise(function(resolve, reject) {
function runNext() {
let i = index;
++inFlightCntr;
fn(items[index], index++).then(function(val) {
++doneCntr;
--inFlightCntr;
results[i] = val;
run();
}, function(err) {
// set flag so we don't launch any more requests
stop = true;
reject(err);
});
}
function run() {
// launch as many as we're allowed to
while (!stop && inflightCntr < maxConcurrent && index < items.length) {
runNext();
}
// if all are done, then resolve parent promise with results
if (doneCntr === items.length) {
resolve(results);
}
}
run();
});
}
You would then use it like this:
let makeAPICall = function(request) {
return axios.post("http://www.api.com/", {
number: `${request.number}`,
webhookURL: "http://localhost:8000/",
});
};
mapConcurrent(calls, 10, makeAPICall).then(results => {
// all results in order here
console.log(results);
}).catch(err => {
console.log(err);
});
See another similar issue here: Promise.all consumes all my RAM
If you really want to run them in fixed batches where the whole batch finishes before you run any more requests, you could do something like this:
const axios = require("axios");
const calls = require("../model/data"); // [{number:1}, {number:2},{number:3},...{number:100}]
function makeAPICall(request) {
return axios.post("http://www.api.com/", {
number: `${request.number}`,
webhookURL: "http://localhost:8000/",
});
};
async function runBatches(array, batchSize, fn) {
let index = 0;
let results = [];
while (index < array.length) {
let promises = [];
for (let num = 0; num < batchSize && index < array.length; ++num) {
promises.push(makeAPICall(array[index++]));
}
let batchResults = await Promise.all(promises);
results.push(...batchResults);
}
return results;
}
runBatches(calls, 10, makeAPICall).then(results => {
// all results in order here
console.log(results);
}).catch(err => {
console.log(err);
});

NodeJS - Nested Promise inside a for loop

I am trying to do a call which retrieves a list of categories. Inside this call I want to loop through the categories and retrieve the items for each category and return them all together. My call retrieves the categories perfectly before I added the loop to retrieve the items.
To double check my call to another controller works, I added a proof of concept block of code which you can see below is commented out. So I know it isn't the call to an external class.
Here is my code:
'use strict';
var mongoose = require('mongoose'),
MenuCategory = mongoose.model('MenuCategory');
module.exports = function(menuItemController) {
var mod = {
listEntireMenu(req, res) {
return new Promise(function(resolve, reject) {
var entireMenu = [];
MenuCategory.find({}, function(err, menuCategories) {
if (err) {
return reject(err)
} else {
//---------------------------
// PROOF OF CONCEPT THAT CALL TO OTHER CONTROLLER WORKS
//---------------------------
//
// var categoryWithItems = menuCategories[0].toObject();
// req.body.menuCategoryID = categoryWithItems._id;
// menuItemController.listAllMenuItemsByCategory(req, res).then((menuItems) => {
// if(menuItems)
// {
// return resolve(menuItems);
// }
// else
// {
// return { success: false }
// }
// });
//-----------------------------
for (var i = 0; i < menuCategories.length; i++) {
var categoryWithItems = menuCategories[i].toObject();
var subItems = [];
req.body.menuCategoryID = categoryWithItems._id;
menuItemController.listAllMenuItemsByCategory(req, res).then((menuItems) => {
if(menuItems)
{
subItems = menuItems;
}
else
{
return { success: false }
}
});
categoryWithItems.tester = { "itemsList" : subItems };
entireMenu.push(categoryWithItems);
}
return resolve(entireMenu)
}
});
}).then((menuCategories) => {
if(menuCategories)
{
return menuCategories
}
else
{
return { success: false }
}
});
},
}
return mod;
};
What I actually get returned is this :
[
{
"_id": "5ed16fxxxxxxxx95676e37",
"locationID": "5ed16xxxxxxxx7295676e36",
"menuCategoryName": "Category One",
"Created_date": "2020-05-29T20:26:34.991Z",
"__v": 0,
"tester": {
"itemsList": []
}
},
{
"_id": "5ed170xxxxxx95676e38",
"locationID": "5ed16xxxxxxxx7295676e36",
"menuCategoryName": "Category Two",
"Created_date": "2020-05-29T20:26:48.799Z",
"__v": 0,
"tester": {
"itemsList": []
}
}
]
Here is the call from the route.js :
app.get('/api/listEntireMenu', (req, res) => {
menuCategoryController.listEntireMenu(req, res).then(menuCategories => res.json(menuCategories));
})
It never writes the subItems into the object. Is this an async issue or something else? I am not sure how to solve this.
Thanks in advance.
i believe the reason the result of your call to resolve is being returned before the requests are able to complete...for this you need to wait until all the promises or requests have finished properly and returned.
There are two ways you can do this: you could either run them one by one and wait for each one to finish first or run them all concurrently until all of them are done.
Ofcourse the fastest way to do it would be to run them all concurrently so lets go for that way:
so to start, let us not use the for loop and instead remap the iterable array menuCategories to promises of the request, we will use your proof of concept code to make the array of promises
//...
Promise.all(
menuCategories.map((category) => {
let category_with_items = category.toObject();
req.body.menuCategoryID = category_with_items._id;
// here we need to return this since its the promise we are remapping to
return menuItemController.listAllMenuItemsByCategory(req, res)
.then((menuitems) => {
if(menuItems) {
return menuitems;
}
throw 'No menu items found'
});
});
)
// each promise will return menuitems so we have to wait for all the promises to complete
// then with the results of each promise we push the items into the entire menu
.then((itemslist) => {
itemslist.forEach((items) => entireMenu.push(items));
return entireMenu;
})
// lastly we need to handle any errors from the promises
.catch((error) => { success: false });
//...
So now we have...
listEntireMenu(req, res) {
return MenuCategory.find({}, function(err, menuCategories) {
if (err) {
throw err
} else {
entireMenu = [];
return /* the promise all call from above will go right here */;
}
}
I hope it works out, thanks...

Wait for foreach to complete NodeJS

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)
}
}

Promise inside async

I am writing a code using Node.js. I want to parse JSON array, retrieve elements from JSON array, make db call and assign values to JSON array. Make this complete operation in synchronous way. For this I wrote code using for loop:
for (let i = 0; i < items.length; i++) {
if(items[i].type === 'PickSimple'){
operation(item.searchSpec)
.then(lov => {
items[i].listOfValues = lov;
})
.catch(err =>{
console.log(err);
});
}
}
console.log("Final OBJ : "+items)
function operation(lov) {
return new Promise((resolve, reject) => {
Listofvalue.find({type: lov}, function(err, listofvalues) {
if (err) {
return reject(err);
}
return resolve(listofvalues);
});
});
But node is asynchronous, I am not getting desired result. So I have used async:
async.each(items,
function(item,callback) {
if(item.type === 'PickSimple'){
operation(item.searchSpec)
.then(lov => {
item.listOfValues = lov;
}).catch(err =>{
console.log(err);
});
}
}, err => {
if (err) console.error(err.message);
}
);
I have also tried using async.forEachOf.
Still I am not getting desired result. Is anything missing?
EDIT
async function processArr(items){
console.log("Inside processArr "+JSON.stringify(items));
for(const item in items){
console.log("Inside for loop, item : "+item);
if(item.type === 'PickSimple'){
var listOfValues = await operation(item.searchSpec)
item.listOfValues = listOfValues;
}
}
console.log("ProcessArr Final OBJ : "+JSON.stringify(items));
}
Output:
Inside processArr [{"name":"Call Related To","type":"PickSimple","searchSpec":"TM_CALL_RELATED_TO_SERVICE"},{"name":"Disposition Codes","type":"Text","searchSpec":""},{"name":"VOC 1","type":"Text","searchSpec":""}]
Inside for loop, item : 0
Inside for loop, item : 1
Inside for loop, item : 2
If you're running Node 8.x+ you can use the async/await. The following for...in should await for promise to complete before iterating to next item.
PS. I've not tested that method, let me know if it works for you.
async function processArr(items){
for(const item in items){
if(items[item].type === 'PickSimple'){
var listOfValues = await operation(items[item].searchSpec)
items[item].listOfValues = listOfValues;
}
}
console.log("Final OBJ : "+items)
}
EDIT:
You're getting undefined because you're calling console.log inside console.log.

how to perform synchronously findOne & create With in foreach Loop?

In the following code, I have an infinite loop which I don't know why it happens. My best guess is because the function inside is async the loop doesn't wait for it and so the loop never stops. What is the best way to solve this issue ?
for (var i = 0, len = studentsData.length; i < len; i++) {
// (function(i) {
// var p = new Promise(function(resolve, reject) {
// var item = studentsData[i];
// Find student
Student.findOne({
email: item.Email
},
function (err, student) {
if (err) {
reject(err);
} else {
if (!student) {
// Create Student.
var StudentObj = {
firstName: item.Name || null,
lastName: null,
contact: item.Mobile || null,
securityToken: UTIL.randomString(21),
email: item.Email
};
Student.create(StudentObj, function (err, newStudent) {
if (err) {
reject(err);
} else {
// return newStudent;
console.log("Student created with Id=", newStudent._id);
// resolve(newStudent);
}
});
}
}
}
);
//});
// studentPromise.push(p);
// })(i);
}**
Without know what studentsData variable is, I would say i is always less than studentsData.
You are making things very complicated for yourself, trying to run a callback function in a for loop like so.
I would be inclined to rewrite this using a promise and calling all the promises at once with promise.all
You could do something like this:
function findOrCreateStudent(item){
// push all promises into an array called promises
const promises = studentsData.map(async (studentData) => {
// first param is your query, second is the object of the new document you want to create
try {
return await Student.findOrCreate({email: item.Email}, studentData) // I'm assuming studentsData is an array of student objects you want to find or create
}catch(err){
console.log(`Unable to find or create studen ${err}`)
}
})
return Promise.all(promises) // call all promises
}

Resources