Wait for the nested loops to finish in nodejs - node.js

I am very new to node.js and here is my problem
I have a nested set of maps here(I have tried using for-each as well).
All the "get..." methods nested are returning a promise defined by me. I have to loop over the data returned by the methods continuously and use it to call the next "get.." method(there is a dependency). After completing the entire nested loops, the "allobj" array should have my final result and I am trying to return it once all the looping is done. I have not had any luck so far, as an empty array gets returned.
var allobj = new Array();
var catcount = 0;
//categories.forEach(function(category){
var p = categories.map(function(category,index,array){
catcount++;
return gettests(runid,category).then(function(tests){
//console.log(tests.sort().reverse()); //this is sync
//tests.forEach(function(test){
var t = tests.map(function(test,index,array){
return getmetriccategories(runid,category,test).then(function(data){
//console.log(data);
//data.forEach(function(mc){
var d = data.map(function(mc,index,array){
return getmetrics(runid,category,test,mc).then(function(data1){
var x = data1.map(function(metric,index,array){
allobj.push(metric);
return metric;
});
});
});
})
})
})
})
//return when all nested loops are done
res.send(allobj);

Your functions getmetriccategories and getmetrics return Promises, at least, their return values seem to have a then method which is an indicator for that. This means, they work asynchronously. So, the map calls don't return the results directly, but an array of Promises. To wait for all of these Promises to be fulfilled (which means, the asynchronous functions are completed), you can use Promise.all function:
Promise.all(data.map(...))
.then(function (result) {
res.send(result)
})
As you see, Promise.all returns a new Promise, so you can use then to receive the result and send it with res.send.

Related

How the while loop works with promise and callback function in Nodejs?

This is my first time to write while loop with Promise and callback. I don't know why it leads to infinite loop. How to fix it?
async function getResult(){
return new Promise((resolve, reject) => {
let params ="some input and setting";
let next = "hasNext";
let array = [];
let error = null;
while(next !== null){
checkNext(params, function(err,data) { //checkNext is a function to return the current list and check wether there has the next list
if(err){
next = null;
error = err;
}else{
next = data.hasNext; // if there is not next list, data.hasNext = null
array = array.concat(data.array); // data.array return the current list
}
});
}
if(error !== null){
reject(error);
}else{
resolve(array); // I want to return all lists
}
});
}
It leads to an infinite loop because checkNext() is asynchronous and non-blocking so your while() just runs forever before even one call to checkNext() gets a chance to finish and call its callback.
You never use a while() loop waiting for some asynchronous things to finish in Javsacript (except with await as shown below) because with the event driven architecture of nodejs, the while loop never returns control back to the event loop so no asynchronous operation can never get its completion event processed and thus the thing you are waiting for never gets a chance to happen. Things are different (as shown below) if you use await to await a promise that is connected to your asynchronous event. Then, you can use a while() loop successfully.
With asynchronous operations where you're going to use promises, you pretty much always want to promisify your asynchronous operations so all your control flow is with promises, not plain callbacks as the two don't mix very well. This is what I would suggest:
const { promisify } = require('util');
const checkNextP = promisify(checkNext);
async function getResult() {
let params = "some input and setting";
let next = "hasNext";
let array = [];
while (next !== null) {
let data = await checkNextP(params);
next = data.hasNext; // if there is not next list, data.hasNext = null
array = array.concat(data.array); // data.array return the current list
}
return array;
}
Here, the while loop works because we're using await with a promise returned from checkNextP() and that await suspends the execution of the function until that promise resolves/rejects.
A little more explanation about how async functions work
At the point we hit the first await this async function will automatically return a promise. The caller will get that promise at that point. Then, when the promise with that first await resolves, the function will resume, you will get the first data value and the rest of your loop executes. This process will repeat until next is null. At that point, your while() loop will be done and the return array statement will execute. Because this is an async function, what that return statement really does is it resolves the promise that the async function previously returned and sets the array to be the resolved value of that promise.
If, the promise from checkNextP() rejects, then the await checkNextP() will throw the rejection and since we don't have a try/catch around it, the async function will automatically catch that throw and it will reject the promise that the async function has previously returned, causing the caller to get a promise rejection with whatever error checkNextP() rejected with. So, the error handling here works too.
The caller of getResult(), just needs to do something like this:
getResult().then(results => {
console.log(results);
}).catch(err => {
console.log(err);
});
Or, the caller could also be in an async function itself and use await and try/catch to catch errors.

About the local variable in Node.js promise

I am a newer of Node.js.I defined a array as a local variable,and want to use it in the following then,I save some useful data in it.But in the end, the array is empty.Can somebody tell me why?Thanks for your support.
const Device = require("./mongo.js").Device;
const Video = require("./mongo.js").Video;
Device.findOne({id:"11112222"}).exec()
.then(function(data){
var videoIds = data.videoIds.split(",");
var videoId2URL = [];
console.log(videoIds);
videoIds.forEach(function(one){
return Video.findOne({id:one}).exec()
.then(function(data){
videoId2URL.push({id:one,url:data.url});
return videoId2URL;
})
});
console.log(videoId2URL);
});
The problem is that you are displaying videoId2URL too early.
Device.findOne returns a promise executed asynchronously. But Video.findOne also returns a promise executed asynchronously.
So when you do console.log(videoId2URL);, the promises created by Video.findOne are not executed yet. So your array is empty.
You must wait the end of all your promises. You can use Promise.all for that.
Promise.all(videoIds.map(function(one){
return Video.findOne({id:one}).exec()
.then(function(data){
videoId2URL.push({id:one,url:data.url});
return videoId2URL;
});
})
.then(function() {
console.log(videoId2URL);
});
You could use Promise.all to resolve your problem. You forEach code contains async code. Your last line does not wait for all promises to get resolved.
Try with:
var arr = [];
videoIds.forEach(function(one){
return arr.push(Video.findOne({id:one}).exec());
});
Promise.all(arr) // here we are waiting for all async tasks to get resolved
.then(function(data){
console.log(data);
// parse your data here and find array of videoId2URL
})
When you do console.log(videoId2URL), you're still in the main stack for the script, while none of the push callbacks have been executed.
You can use an array to collect the promises returned by Video.findOne, and at the end use Promise.all to drain all the promises and do the log then.
BTW, none of the 2 return are necessary, you can safely remove them.
The 1st one is not used because it's in a synchronous callback for forEach.
The 2nd one is not used because you're relying on the side effect, rather than use the resolved value.
Try:
const Device = require("./mongo.js").Device;
const Video = require("./mongo.js").Video;
Device.findOne({id:"11112222"}).exec()
.then(function(data){
var videoIds = data.videoIds.split(",");
var videoId2URL = [];
var promiseArr = [];
console.log(videoIds);
videoIds.forEach(function(one){
var p = Video.findOne({id:one}).exec()
.then(function(data){
videoId2URL.push({id:one,url:data.url});
});
promiseArr.push(p);
});
Promise.all(promiseArr).then(function() {
console.log(videoId2URL);
});
});

Node.js - Continue promise chain for each result after promise.all()

I am using Promise.all() within a promise chain. Each promise within the Promise.all() returns a string.
The issue im having is that Promise.all() returns a Promise object to the next promise and I would like to continue the promise chain for each string.
Heres an example:
....
return Promise.all(plugins);
})
.then(function(response) {
console.log(response)
....
The response looks like:
[ 'results from p1', 'results from p2' ]
Is there any way to continue the promise chain for each of the results rather than continuing with a single object containing all results?
Promise.all expects an array of promises. So plugins is an array of promises and all the more: plugin is a Promise.
So you can just chain your plugin Promise.
This would thus become
Promise.all(plugins.map(function(plugin){
return plugin.then(function(yourPluginString){
return 'example '+ yourPluginString;
})
}))
Promise.all(), by its design returns a single promise who's resolved value is an array of resolved values for all the promises you passed it. That's what it does. If that isn't what you want, then perhaps you are using the wrong tool. You can process the individual results in a number of ways:
First, you can just loop over the array of returned results and do whatever you want to with them for further processing.
Promise.all(plugins).then(function(results) {
return results.map(function(item) {
// can return either a value or another promise here
return ....
});
}).then(function(processedResults) {
// process final results array here
})
Second, you could attach a .then() handler to each individual promise BEFORE you pass it to Promise.all().
// return new array of promises that has done further processing
// before passing to Promise.all()
var array = plugins.map(function(p) {
return p.then(function(result) {
// do further processing on the individual result here
// return something (could even be another promise)
return xxx;
});
})
Promise.all(array).then(function(results) {
// process final results array here
});
Or, third if you don't really care when all the results are done and you just want to process each one individually, then don't use Promise.all() at all. Just attach a .then() handler to each individual promise and process each result as it happens.
You can use a tool like https://github.com/Raising/PromiseChain
and implement what you say as
//sc = internalScope
var sc = {};
new PromiseChain(sc)
.continueAll([plugin1,plugin2,plugin3],function(sc,plugin){
return plugin(); // I asume this return a promise
},"pluginsResults")
.continueAll(sc.pluginsResults,function(sc,pluginResult){
return handlePluginResults(pluginResult);
},"operationsResults")
.end();
I didnt test the code if you have any problem PM me

For loop in redis with nodejs asynchronous requests

I've got a problem with redis and nodejs. I have to loop through a list of phone numbers, and check if this number is present in my redis database. Here is my code :
function getContactList(contacts, callback) {
var contactList = {};
for(var i = 0; i < contacts.length; i++) {
var phoneNumber = contacts[i];
if(utils.isValidNumber(phoneNumber)) {
db.client().get(phoneNumber).then(function(reply) {
console.log("before");
contactList[phoneNumber] = reply;
});
}
}
console.log("after");
callback(contactList);
};
The "after" console log appears before the "before" console log, and the callback always return an empty contactList. This is because requests to redis are asynchronous if I understood well. But the thing is I don't know how to make it works.
How can I do ?
You have two main issues.
Your phoneNumber variable will not be what you want it to be. That can be fixed by changing to a .forEach() or .map() iteration of your array because that will create a local function scope for the current variable.
You have create a way to know when all the async operations are done. There are lots of duplicate questions/answers that show how to do that. You probably want to use Promise.all().
I'd suggest this solution that leverages the promises you already have:
function getContactList(contacts) {
var contactList = {};
return Promise.all(contacts.filter(utils.isValidNumber).map(function(phoneNumber) {
return db.client().get(phoneNumber).then(function(reply) {
// build custom object
constactList[phoneNumber] = reply;
});
})).then(function() {
// make contactList be the resolve value
return contactList;
});
}
getContactList.then(function(contactList) {
// use the contactList here
}, funtion(err) {
// process errors here
});
Here's how this works:
Call contacts.filter(utils.isValidNumber) to filter the array to only valid numbers.
Call .map() to iterate through that filtered array
return db.client().get(phoneNumber) from the .map() callback to create an array of promises.
After getting the data for the phone number, add that data to your custom contactList object (this is essentially a side effect of the .map() loop.
Use Promise.all() on the returned array of promises to know when they are all done.
Make the contactList object we built up be the resolve value of the returned promise.
Then, to call it just use the returned promise with .then() to get the final result. No need to add a callback argument when you already have a promise that you can just return.
The simplest solution may be to use MGET with a list of phone numbers and put the callback in the 'then' section.
You could also put the promises in an array and use Promise.all().
At some point you might want your function to return a promise rather than with callback, just to stay consistent.
Consider refactoring your NodeJS code to use Promises.
Bluebird is an excellent choice: http://bluebirdjs.com/docs/working-with-callbacks.html
you put async code into a for loop (sync operations). So, each iteration of the for loop is not waiting for the db.client(...) function to end.
Take a look at this stackoverflow answer, it explains how to make async loops :
Here

Returning an Array using Firebase

Trying to find the best-use example of returning an array of data in Node.js with Q library (or any similar library, I'm not partial) when using Firebase .on("child_added");
I've tried using Q.all() but it never seems to wait for the promises to fill before returning. This is my current example:
function getIndex()
{
var deferred = q.defer();
deferred.resolve(new FirebaseIndex( Firebase.child('users').child(user.app_user_id).child('posts'), Firebase.child('posts') ) );
return deferred.promise;
}
function getPost( post )
{
var deferred = q.defer();
deferred.resolve(post.val());
return deferred.promise;
}
function getPosts()
{
var promises = [];
getIndex().then( function (posts) {
posts.on( 'child_added', function (_post) {
promises.push( getPost(_post) );
});
});
return q.all(promises);
}
The problem occurs in getPosts(). It pushes a promise into your array inside an async function--that won't work since q.all is called before the promise objects have been added.
Also, child_added is a real-time event notification. You can't use that as a way to grab "all of the data" because there is no such thing as "all"; the data is constantly changing in real-time environments. FirebaseIndex is also using child_added callbacks internally, so that's not going to work with this use case either.
You can grab all of the posts using the 'value' callback (but not a specific subset of records) as follows:
function getPosts() {
var def = q.defer();
Firebase.child('users').once('value', function(snap) {
var records = [];
snap.forEach(function(ss) {
records.push( ss.val() );
});
def.resolve(records);
});
return def.promise;
}
But at this point, it's time to consider things in terms of real-time environments. Most likely, there is no reason "all" data needs to be present before getting to work.
Consider just grabbing each record as they come in and appending them to whatever DOM or Array where they need to be stored, and working from an event driven model instead of a GET/POST centered approach.
With luck, you can bypass this use case entirely.

Resources