Undefined variables within nested function - javascript

I have a http post request which returns ID. I then try to pass that ID into another function. However, inside the next function I have a timeout that will loop the function to check the status. The ID returns undefined each time inside the timeout function.
First Function
Here I have 'res' which is a result from another function. I grab the status ID from the returned json and send it to 'getAlbum'.
anotherFunction(res) {
this.getAlbum(res);
}
GetAlbum
If I do a console log immediately inside this function, it correct emits the correct ID. However, if I do it inside the 'checkAblumStatus' function, the id part is undefined.
getAlbum(id){
var statusID = id.status_id;
console.log('id = ' + statusID) // returns id
var statusIDRequest = 'url' + statusID;
var checkAblumStatus = function (statusIDRequest) {
console.log('statusIDRequest = ' + statusIDRequest) // returns undefined for the ID part
this.http.get(statusIDRequest).subscribe(res => {
if (res.status == "completed") {
// completed
} else if (res.status == "failed") {
// failed
} else {
setTimeout(checkAblumStatus, 1000);
}
});
};
setTimeout(checkAblumStatus, 1000);
}
Any help here would be very grateful :)

This happens because of the scope of your variables.
var checkAblumStatus = function (statusIDRequest) {
console.log('statusIDRequest = ' + statusIDRequest) // returns undefined for the ID part
this.http.get(statusIDRequest).subscribe(res => {
if (res.status == "completed") {
// completed
} else if (res.status == "failed") {
// failed
} else {
setTimeout(checkAblumStatus, 1000);
}
});
};
In the context of your function, this references the function itself, not your object.
You need to use a closure or a fat arrow like this.
var checkAblumStatus = (statusIDRequest) => {
You also need to provide a avariable to your calls.
setTimeout(checkAblumStatus(variable), 1000);

You get confused with variable and param name.
var statusIDRequest = 'url' + statusID;
^ ^ // this variable
var checkAblumStatus = function (statusIDRequest) {
^ ^ // .. is not the same not this param
Change the name of the variable like this, so you don't get rid of the name:
getAlbum(id){
var statusID = id.status_id;
console.log('id = ' + statusID) // returns id
var statusID = 'url' + statusID;
var checkAblumStatus = function (statusIDRequest) {
console.log('statusIDRequest = ' + statusIDRequest) // returns the ID part
this.http.get(statusIDRequest).subscribe(res => {
if (res.status == "completed") {
// completed
} else if (res.status == "failed") {
// failed
} else {
setTimeout( () => checkAblumStatus (statusIDRequest), 1000);
}
});
};
setTimeout(() => checkAblumStatus(statusID), 1000);
}

you can pass the id for the function as follow
function getAlbum(id){
var statusID = id.status_id;
console.log('id = ' + statusID) // returns id
var statusIDRequest = 'url' + statusID;
var checkAblumStatus = ((statusIDRequest) => {
console.log('statusIDRequest = ' + statusIDRequest) // returns undefined for the ID part
this.http.get(statusIDRequest).subscribe(res => {
if (res.status == "completed") {
// completed
} else if (res.status == "failed") {
// failed
} else {
setTimeout(checkAblumStatus, 1000);
}
});
})(statusIDRequest);
setTimeout(checkAblumStatus, 1000);
}

Related

Atomically incrementing a shared counter in Firebase realtime database

I need to decrement a counter (named credits) stored in Firebase real time database.
To decrement the counter I do like this:
var ref = admin.database().ref('licenseCredits/' + name + '/credits');
ref.transaction( (value) => {
if (value === null) {
return 0;
} else if (typeof value === 'number') {
return value - 1;
} else {
console.log('The counter has a non-numeric value: ');
}
});
The credits field is being correctly decremented.
I put this code into a callable function but I don't know how to return the decremented value to the caller. If I simply return the ref.transaction result I get a "Unhandled RangeError exception".
As per the documentation below, the there should be a onComplete function implemented.
https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction
var ref = admin.database().ref('licenseCredits/' + name + '/credits');
ref.transaction( (value) => {
if (value === null) {
return 0;
} else if (typeof value === 'number') {
return value - 1;
} else {
console.log('The counter has a non-numeric value: ');
}
}, function(error, committed, snapshot) {
if (error) {
console.log('Transaction failed abnormally!', error);
} else if (!committed) {
console.log('We aborted the transaction.');
} else {
console.log('Success!');
}
console.log("Credit data: ", snapshot.val());
});
At the end I found a way to tackle the problem taking into account #chris answer.
I used a javascript promise implemented using the 'kew' library.
Here is the working code:
var qTrans = Q.defer();
var ref = admin.database().ref('licenseCredits/' + name + '/credits');
var credits = 0;
ref.transaction( (value) => {
if (value === null) {
// the counter doesn't exist yet, start at one
return 1;
} else if (typeof value === 'number') {
// increment - the normal case
return value + 1;
} else {
// we can't increment non-numeric values
console.log('The counter has a non-numeric value: ' + JSON.stringify(value));
// letting the callback return undefined cancels the transaction
}
}, (error, committed, snapshot) => {
if (error) {
console.log('Transaction failed abnormally!', error);
qTrans.reject(error);
} else if (!committed) {
console.log('We aborted the transaction.');
qTrans.reject(error);
} else {
console.log('Success!');
console.log("Credit data: ", snapshot.val());
qTrans.resolve(snapshot.val());
}
});
return qTrans.promise;

Why is the Axios .then and .catch function incrementing my index?

I have a loop that is making up to five calls to an API endpoint to validate ids. My increment variable turns from 0 to 1 on the first iteration before finishing the loop.
I pinpointed that it happens whether the request is good or bad after console logging the variable right before the request and then inside each callback. As soon as the variable is called in the .then callback or the .catch callback, the index is incremented and I have no idea why. I have tested different variable names and still get the same result. Anyone have an idea on this?
I also used the .fetch() method with React and the same thing is happening in the .then function so I don't think this is specific to axios.
Here is my function:
isValidAIN(ains) {
var control = this;
var length = ains.length;
if (ains.length > 0) {
for (var i = 0; i < length; i++) {
if (ains[i].length !== 10) {
if (ains[i].length === 0) {
this.state.errors["ain[" + i + "]"] = "";
this.state.validAINS[i] = true;
} else {
this.state.errors["ain[" + i + "]"] = "This AIN Number Must Contain 10 Digits";
this.state.validAINS[i] = false;
}
this.setState(this.state);
} else {
// v this logs 0
console.log("i: ", i);
axios
.get("/myendpoint/?ain=" + ains[i])
.then((res) => {
// v this logs 1
console.log("in then: ", i);
console.log("res: ", res);
control.state.errors["ain[" + i + "]"] = "";
control.state.validAINS[i] = true;
control.setState(control.state);
})
.catch((err) => {
// v this logs 1
console.log("i in catch", i);
if (err.response.status == 404) {
control.state.errors["ain[" + i + "]"] = "AIN Is Invalid";
console.log(control.state.errors["ain[" + i + "]"]);
control.state.validAINS[i] = false;
control.setState(control.state);
return false;
}
});
// fetch("/myendpoint/?ain=" + ains[i])
// .then(res => res.json())
// .then(
// (result) => {
// console.log("success: ", result);
// if(result == null) {
// control.state.errors["ain[" + i + "]"] = "AIN Is Invalid";
// control.state.validAINS[i] = false;
// control.setState(control.state);
// return false;
// }
// else {
// control.state.errors["ain[" + i + "]"] = "";
// control.state.validAINS[i] = true;
// control.setState(control.state);
// }
// },
// (error) => {
// console.log("error: ", error);
// }
// )
// .catch(
// (err) => {
// console.log("Error from catch: ", err);
// }
// )
}
}
} else {
return false;
}
}
The axios request is asynchronous, and the scope of variables declared with var is the enclosing function. When the axios request is complete, this variable will be the max of the loop, since the loop is synchronous and complete.
Change the var to let for i to use block scoping instead.
for(let i = 0; i < length; i++) { ... }
Because axios calls are asynchronous.
You're performing a for loop that just loops through ains.length, this happens in a few milliseconds. You define i as a var that is incremented after every loop within the for loop.
So if you're in the then / catch, the variable i is already at its maximum (so 1 in your case).
i is a variable and not a constant. When using i you're referencing to that pointer in memory. If you want i to be unique in every loop you should define it as let instead of var. This will create a unique value of i for each invocation of the loop.

Javascript - Creates new array instead of incrementing

I'm making a simple twitter app to work on my javascript.
The code below is supposed to identify every tweets location and count the number of tweets per location.
However, it doesn't increment, it just creates a new array.
What is wrong with my code? How can I make it better?
Thank you
var Twitter = require('node-twitter'),
twit = {},
loc = [];
twit.count = 0;
var twitterStreamClient = new Twitter.StreamClient(
//credentials
);
twitterStreamClient.on('close', function () {
console.log('Connection closed.');
});
twitterStreamClient.on('end', function () {
console.log('End of Line.');
});
twitterStreamClient.on('error', function (error) {
console.log('Error: ' + (error.code ? error.code + ' ' + error.message : error.message));
});
twitterStreamClient.on('tweet', function (tweet) {
if (loc.indexOf(tweet.user.location) === -1) {
loc.push({"location": tweet.user.location, "locCount": 1});
} else {
loc.loation.locCount = loc.loation.locCount + 1;
}
console.log(loc);
});
var search = twitterStreamClient.start(['snow']);
You need to rewrite on tweet callback:
var index = loc.reduce(function(acc, current, curIndex) {
return current.location == tweet.user.location ? curIndex : acc;
}, -1);
if (index === -1) {
loc.push({"location": tweet.user.location, "locCount": 1});
} else {
loc[index].locCount++;
}
Array.indexOf is not matching as you think it is. You're creating a new object and pushing it into the array, and regardless of whether its properties match a different object perfectly, it will not be === equal. Instead, you have to find it manually:
var foundLoc;
for (var i = 0; i < loc.length; i++) {
if (loc[i].location.x === location.x)
foundLoc = loc[i];
break;
}
}
if (!foundLoc) {
loc.push({location: location, count: 0});
} else {
foundLoc.count++
}

Need help comparing the value inside array, with forEach loop

So basically I have some promise, forEach, just a lot of issues with this single problem I need to solve. So the variables I work with has below structure:
persons = [object, object, object]
where each object has { user:number , username: string, latitude:number, longitude:number}
from there I try to figure out if my user/username is inside of one of these objects, if not found id like it to be created, if found found id like it to update their location. Sounds simple, I think the problem has blown out of proportion but nothing works. The code I have now does not work, its either I can never figure out when the user is not there, or I can not figure out how to get to stop creating me every time it find a user who is not me.
var getswamp = function(item, index) {
return new Promise(function(resolve, reject){
var result = false;
if (item.user === user && item.username === username) {
if ((item.latitude !== latitudenew) || (item.longitude !== longitudenew)) {
var id = item.id;
swampdragon.update('locationcurrent', {
user: user,
latitude: latitudenew,
longititude: longitudenew,
username: username,
id: id
}, function (context, data) {
console.log("data updated", data);
result = true;
resolve(result);
}, function (context, data) {
console.log("You may not be updated");
});
} else {
console.log("No location change");
result = true;
}
}else{
if ( item.index === person.index){
console.log(person);
resolve(result)
}
}
});
};
person.forEach(function (item, index) {
var swamping = getswamp(item, index);
swamping.then(function (result) {
console.log(result);
if (result === true) {
console.log("We have you");
} else if (result === false && (index === person.length - 1)) {
console.log('Index: ' + index + ' Length of list is ' + person.length);
swampdragon.create('locationcurrent', {
user: user,
latitude: latitudenew,
longititude: longitudenew,
username: username
}, function (context, data) {
console.log("data created", data);
}, function (context, data) {
console.log("You may not be created")
});
}
})
});
Any help/ideas would just be great.
The Promise is used when some asynchronous event happened.
Since I can not create such event, I made a static code as below:
var persons = new Array();
persons.indexOf = function(person) {
var index = -1;
this.forEach(function(obj, i) {
if (person.username == obj.username) {
index = i;
}
});
return index;
}
persons.addOrUpdate = function(person) {
var index = this.indexOf(person);
if (index == -1) {
person.user = this.length + 1;
this.push(person);
}
else { // update case
var obj = this[index];
obj.latitude = person.latitude;
obj.longitude = person.longitude;
}
}
persons.print = function() {
var str = "";
this.forEach(function(obj) {
str += obj.user + ". " + obj.username + " at location (" +
obj.latitude + ":" + obj.longitude + ")\n";
});
return str;
}
persons.addOrUpdate({username:'Adam', latitude:-0.0045, longitude:14.2015});
persons.addOrUpdate({username:'Eve', latitude:-0.0045, longitude:14.2015});
persons.addOrUpdate({username:'Abel', latitude:-0.0045, longitude:14.2015});
// Updating Able location
var person = {username:'Abel', latitude:10.1145, longitude:14.1234};
persons.addOrUpdate(person);
alert(persons.print());

How can I access a variable in parent function?

I want to access the data in document in the function were I handle the Item, How can I put the variable document.date in the itemstring?
function pagelist(items, res) {
var db = db_login;
result = "<html><body><ul>";
items.forEach(function(item) {
console.log(item)
db.collection('insights').findById(item._id , function(error, document) {
console.log(document)
if (error || !document) {
res.render('error', {});
} else {
**console.log(document.date) //this value is displayed**
}
})
**console.log(document.date)//this value is undefind**
itemstring = "<li>" + item._id + "<ul><li>" + item.textScore +
"</li><li>" + item.created + "</li><li>" + item.document +
"</li></ul></li>";
result = result + itemstring;
});
result = result + "</ul></body></html>";
return result;
}
The current answers all miss a key point, that your "child" function is a callback from an asyn function. It does not execute before the rest of your "parent" function does.
function pagelist(items, res) {
// ... (1)
items.forEach(function(item) {
db.collection('insights').findById(item._id, function(error, document) {
// this will not execute (4)
});
// before this (2)
});
// or this (3)
// Actual order will be in that of (numbers)
return result;
}
Your only option is you make your parent function behave the same way that your db function does. I.e. make it asyn too.
function pagelist(items, done) { // <= "done"
items.forEach(function(item) {
db.collection('insights').findById(item._id, function(error, document) {
// Now you can call done with document that is available here
done(document); // or done(document.date); w/e
});
// return result; // Returns are useless* in asyncs
}
Wherever you're calling pagelist() from pass it a callback function "done" and do stuff in there
app.use(function(req, res){
pagelist(items, function(document){ // <= this will be the "done" function
var itemstring = document.date;
// do whatever you want with the document here.
res.render(~);
console.log(document.date);
});
});
Try this:
Assign local variable document to global variable doc and access it in outside of the function;
function pagelist(items, res) {
var db = db_login;
var doc;
result = "<html><body><ul>";
items.forEach(function(item) {
console.log(item)
db.collection('insights').findById(item._id , function(error, document) {
console.log(document)
doc=document; // assignment of the local variable to global variable
if (error || !document) {
res.render('error', {});
} else {
console.log(document.date)
}})
console.log(doc.date) // here is use of global variable
itemstring = "<li>" + item._id + "<ul><li>" + item.textScore +
"</li><li>" + item.created + "</li><li>" + item.document +
"</li></ul></li>";
result = result + itemstring;
});
result = result + "</ul></body></html>";
return result;
}
Enjoy :)

Categories

Resources