Pass outer loop parameter to a Promise resolve - node.js

I have a for loop that queries a database. I want the db calls to be async.
This is the structure of the code:
for(var idx=0; idx<arr.rows.length; idx++)
{
db.query(`SELECT ...`)
.then((result) => {
console.log("Value is: " + result.rows[idx].val);
});
}
As you can see, I want the parameter idx printed in the resolve (db.query returns a Promise). But this way the wrong idx is printed because the idx value is incremented when the promise is resolved.
What is the proper way to pass the variable idx?
Thank you.

Use let instead of var.
Change from this:
for(var idx=0; idx<arr.rows.length; idx++)
to this:
for(let idx=0; idx<arr.rows.length; idx++)
let is block-scoped to the block of the for loop and will maintain a separate copy of the variable for each iteration of the loop so that each one has its own and thus your asynchronous .then() handler that gets called after the for loop is completely done will still have the appropriate value in its own copy of idx.

Related

Nested loop Synchronous or Asynchronous?

I have an array called noNCreatedResources. I want do some operation on each item of array and push item in createdResources array and remove the item from noNCreatedResources array and continue to do that until noNCreatedResources be empty. For this I've written CreateResources function including nested while and for loop. It works fine but i realize that it don't work synchronously. For example: it must iterate twice in while loop but iterates 4 times and I don't know why.
I think I don't understand concept of async/await/non-blocking concept of node.js. can any body help me to realize what the problem is?
CreateResources = async () => {
while (this.noNCreatedResources.length > 0) {
for (let index = 0; index < this.noNCreatedResources.length; index++) {
if (this.resourceHasCreatedDependencies(this.noNCreatedResources[index])) {
const resourceModel = this.someOperation(this.noNCreatedResources[index]);
this.createdResources.push(resourceModel);
this.noNCreatedResources.splice(index, 1);
}
}
}
}
First of all you are not doing anything asynchronous in you function so you can remove the async keyword from your function. Since you are not doing anything asynchronous so, your problem is not related to it. It is more of an implementation problem IMO.
Your while loop is useless for what you are trying to achieve. Also, your logic is broken!
Example: The following code will output 1, 3, and 5.
let x = [1,2,3,4,5];
for(let i = 0; i < x.length; i++) {
console.log(x[i]);
x.splice(i, 1);
}
I do not think you need to remove item from array to achieve your expected result. If you need to reset the array then at the end you can just do this x = [] to reset the array.
The problem you have is not due to async calls. Actually, your code is entirely synchronous. Try to take a look at where the "noNCreatedResources" is been created/updated. Async calls happens when you're sending a http request, reading a file etc, in other words, operations that doesn't happens inside your code. It allows the code to go on, not blocking the next function calls, and when the promise is fulfilled, the callback function is invoked.

protractor FOR loop using expectations

I made loop and now I want to use protractor expectation for every i.
Loop works ok, but expectations doesn't. If count is 4, there should be 4 expectations. If I run a test, I get pass, without any expectations(which should be false).
I found articles about that but I couldn't make it happen. I tried with Push, but there is only empty value.
Thanks for help.
myelement.count().then(function(count){
console.log("whatever", count);
for (var i=0; i<count; i++){
var o = location.get(i);
expect(o.getText()).toEqual("something");
};
});
Be aware. Almost all commands are promises. If you don't handle them correct it will never work. You will first need to resolve them before you can go to the next one.
Something like this can do the trick
let promise;
const promises = [];
myelement.count()
.then(function(count) {
console.log("whatever", count);
for (var i = 0; i < count; i++) {
var o = location.get(i);
promises.push(expect(o.getText()).toEqual("something"));
};
return Promise.all(promises);
});
Full test-case code would help, but I assume that you've forgot to either return a promise from 'it' or define and call 'done' callback, since what you're doing - is an async operation.
Your problem descrption is not clear. I'm assuming that location is list of elements. So i'm applying each() on set elements called 'location' . It will work perfectly.
//replace location with myelement if it the correct one
location.each(function(ele,index){
expect(ele.getText()).toEqual("something");
});

Node Js getting an issue while executing for loop

I am using node js to develop my application. In controller i used a for loop and then wrote some code, but in for loop i am executing a query. So before that query completely executed once the code after for loop executing. See below for example.
var commcount = [];
for (var j = 0, len = exlist.length; j < len; j++) {
console.log('exlist[j].id '+exlist[j].id);
ExModel.find({EXModel_ID:exlist[j].id, VALUE: {'$ne':''}},{_id:0}, {sort: {CREATION_DATE: -1}}, function(err1, examplist) {
if (err1) {
console.log('sub error '+err1);
commcount.push(0);
} else {
console.log('examplist.length '+examplist.length);
commcount.push(examplist.length);
}
});
}
console.log('commcount length '+commcount.length);
In above code let us assume exlist.length is 5 so, for loop will be executed for 5 times. In the output console, First exlist[j].id value displaying all 5 times then commcount length value displaying as 0 then only examplist.length value diplaying. So before query executed the for loop for other statements and statements after for loop are being executed. Please help me to solve this. I want the length of result list of query in for loop to be stored in array and want to display those array values after for loop.
First of all you should know that you are calling asynchronous function, whatever the result you are getting is correct, The asynchronous function will run in separate thread, we can not guarantee the out put. Your code is similar to the below code, Let's run the below code
function run(){
console.log("d");
setTimeout(function(){
console.log("a");
setTimeout(function(){
console.log("b");
});
});
console.log("c");
}
The output will be d c a b.

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

Variable Scope in Asynchronous Node.js Loop

I am trying to run some database queries (using sails.js) on an array and upon the queries' return, do something. I figured the best way to do so would be to use a for loop and resolve the promises async, and once they've all resolved, continue on. However, only the last promise in my array is resolving, and it is resolving multiple times because in each 'User.findOne...' then function, the index is array.length-1.
My questions:
How does variable scope in asynchronous loops work? Best resources to explain this?
What is the best way to solve my problem? Why?
Are there any other patterns I should use or not use? I am fairly new to promises and async js, so any tips would be helpful!
Main tutorials I've checked
https://github.com/kriskowal/q
https://github.com/kriskowal/q/wiki/API-Reference
https://github.com/bellbind/using-promise-q/
Thank you for your help!
My simplified code:
functionWhichReturnsPromise()
.then(function(user){
var promises = [];
Q.try(function(){
for (var index in array) {
var fbid = array[index];// Get fbid from array
promises.push(Q.defer().promise); // Add promise to promise array
// Find userid from fbid; resolve respective promise when finished
User.findOne({facebook_id: fbid}).then(function(userSeen){
promises[index].resolve(userSeen.id);
sails.log('resolved where id=' + userSeen.id); // correct
sails.log('resolved where index=' + index); // PROBLEM: always last index
});
}
}).then(function(){
// For debugging purposes
Q.delay(1000).then(function(){
sails.log(promises[0]); // Unresolved
sails.log(promises[1]); // Unresolved
sails.log(promises[2]); // Only last promise in array is resolved
});
// When the userids have been extracted from above (promises fulfilled)...
Q.all(promises).then(function(seenids){
// Do stuff here (Doesn't get here)
});
});
});
In Javascript, variable's scope is function and not curly braces.
Therefore in the following code, the scope of var index is not the for loop's curly braces, the scope is actually the function in which the for loop exists.
Q.try(function(){
for (var index in array) {
var fbid = array[index];// Get fbid from array
promises.push(Q.defer().promise); // Add promise to promise array
// Find userid from fbid; resolve respective promise when finished
User.findOne({facebook_id: fbid}).then(function(userSeen){
promises[index].resolve(userSeen.id);
sails.log('resolved where id=' + userSeen.id); // correct
sails.log('resolved where index=' + index); // PROBLEM: always last index
});
}
})
Within for loop you call the async function, in your case its mongodb call (findOne).
You should always assume that these async function can take any number of milliseconds to run (depends on the function). But in general, usually the loop would have completed before the async functions run. Your for loop fires all those async functions even before those functions start running. The issue is that all those async functions which area pending are still pointing towards that variable index. And that variable is common to all of them because index was in scope of the outer function.
This is a problem created somewhat because of closures in Javascript. And to solve this, we need to use more closures.
There are many resources on the topic of closures that you can google. But go thru the MDN's description of it.
If you capture value of index within another function inside the loop, then you will be good to go.
Here is my suggested solution to your issue. I haven't tested it though, but you get the idea.
Q.try (function () {
array.forEach( function(ele, idx, array) {
(function(index) {
var fbid = array[index]; // Get fbid from array
promises.push(Q.defer().promise); // Add promise to promise array
// Find userid from fbid; resolve respective promise when finished
User.findOne({
facebook_id : fbid
}).then(function (userSeen) {
promises[index].resolve(userSeen.id);
sails.log('resolved where id=' + userSeen.id); // correct
sails.log('resolved where index=' + index); // PROBLEM: always last index
});
})(idx);
})
})
Hope this helps.
Also Note: it is incorrect to use for...in for iterating through arrays.

Resources