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.
Related
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;
I have a loop to verify up to 5 ids if they actually exist. I have a for loop that iterates over the ids, makes a call to the endpoint to check, and does some functionality with the results.
The problem I am having is that the loop is only running one time then seems to return out of the function. My function is as follows:
isValidAIN(ains) {
var control = this;
var length = ains.length;
//length is logging correctly when the function is called.
console.log("length: ", length);
if (length > 0) {
for (let i = 0; i < length; i++) {
//this runs once only
console.log("i: ", i);
if (ains[i].length !== 10) {
//this doesn't run
console.log("length: ", ains[i].length);
console.log("object in length: ", ains[i].length);
console.log(ains[i]);
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 {
//this log only runs once
console.log("this should run for every iteration but only runs once");
fetch("/myendpoint/?ain=" + ains[i])
.then(res => res.json())
.then(
(result) => {
console.log("success: ", result);
if (result == null) {
//This logs to 0 for the first iteration and then does not repeat. I am purposely entering invalid ids to hit this area.
console.log("i: ", i);
control.state.errors["ain[" + i + "]"] = "AIN Is Invalid";
control.state.validAINS[i] = false;
control.setState(control.state);
} else {
//not hitting this area
console.log("res: ", result);
control.state.errors["ain[" + i + "]"] = "";
control.state.validAINS[i] = true;
control.setState(control.state);
}
},
(error) => {
//not hitting this area
console.log("error: ", error);
}
)
}
}
} else {
return false;
}
}
I finally realized my mistake in this commenting the code out line by line. The validAINS array was initialized as an empty array but never actually initialized with values. So the api call had nothing to do with the problem but trying to set the indexes of the empty array to values was breaking the loop. Initializing the array in ComponentDidMount function fixed the issue.
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);
}
I am trying to get a bunch of ID's from an API and then form a sequence of requests which would make further calls to an API to fetch some parameters. These would be totaled and i expect the output results to be pushed as JSON array.
The problem is REST call is async and i've put a promise but not sure when to resolve the promise back to the calling function, the rest call some times take a second or 2 to respond back.
I would like know at what point can i resolve the promise or how to know when the totals have been computed ?
The Route
app.get("/sonar/:x_id",function(req,resp) {
getRestSonar(req.params.x_id).then(function (fromResolve) {
resp.send(fromResolve);
});
});
The function with promise which makes the rest call loops
var getRestSonar = function(requestX) {
return new Promise(function(resolve,reject) {
var unirest = require("unirest");
var reqx = unirest("GET", "http://sonarqubexxServer/api/projects");
var outputJson = {
table: []
};
reqx.end(function (res) {
if (res.error) throw new Error(res.error);
// console.log(res.body);
var result = res.body;
//var needle = req.params.csi_id;
var needle = requestX;
var TotalDuplicateLines = 0;
var TotalBugs = 0;
var TotalNcloc = 0;
var TotalCodeSmells = 0;
var TotalVulnerabilities = 0;
for (var i=0;i<result.length;i++) {
if (result[i].nm.indexOf(needle) !== -1) {
console.log(result[i].k);
var queryUrl = "http://sonarqubexxServer/api/resources?resource="+result[i].k+"&metrics=code_smells,bugs,vulnerabilities,ncloc,coverage,duplicated_lines&format=json"
console.log(queryUrl);
var subrequest = unirest("GET",queryUrl);
subrequest.end(function (resXX) {
if (resXX.error);
var resXXResult = resXX.body;
for (var i=0;i<resXXResult.length;i++) {
// var duplicateData = resXXResult[0].msr.filter(item => item.key == 'duplicated_lines');
resXXResult[i].msr.forEach(m => {
if (m.key === 'duplicated_lines') {
console.log('Duplicated Lines ' + m.val);
TotalDuplicateLines += m.val;
}
else if(m.key === 'bugs' ) {
console.log('Bugs ' + m.val);
TotalBugs += m.val;
}
else if(m.key === 'ncloc' ) {
console.log('Lines of Code ' + m.val);
TotalNcloc += m.val;
}
else if(m.key === 'code_smells' ) {
console.log('Code Smells ' + m.val);
TotalCodeSmells += m.val;
}
else if(m.key === 'vulnerabilities' ) {
console.log('Vulnerabilities ' + m.val);
TotalVulnerabilities += m.val;
outputJson.table.push({totduplines:TotalDuplicateLines},{totVul:TotalVulnerabilities});
}
});
console.log("Iam here with I :: " + i);
if (i === (resXXResult.length - 1)) {
//Should i resolve here makes no sense
console.log("Resolved the promise now..");
}
//The for ends here
}
// I see this is a bad place to resolve..
resolve(outputJson);
});
}
}
});
});
}
EDIT : As suggested in the comments, split the calls into smaller
sections
Now, i fetch the api calls seperatly create an array out of it, then use promises to call back to the API ? how do i resolve each call by looping over it ?
When i try to loop it always resolves request[0] and then comes out of the promise, how can i create a promise array and wait for them to complete ?
app.get("/sonar/:csi_id",function(req,resp) {
var collectiveResult = [];
getRestSonar(req.params.csi_id).then(function (fromResolve) {
return splitReqUrl(fromResolve);
}).then(function(fromSplitUrl) {
console.log("I am from split url ::::" + fromSplitUrl);
return getSubSonarProperties(fromSplitUrl);
}).then(function(fromsubSonar) {
collectiveResult.push(fromsubSonar);
console.log("+++++++++++++++++++++++++++");
console.log(fromsubSonar);
resp.send(collectiveResult);
});
});
var getSubSonarProperties = function(getUrl) {
return new Promise(function(resolve,reject) {
var getSubRest = require("unirest");
console.log("Attempting to GET " + getUrl);
var req = getSubRest("GET",getUrl);
var outputJson = {
table: []
}
var TotalDuplicateLines = 0;
var TotalBugs = 0;
var TotalNcloc = 0;
var TotalCodeSmells = 0;
var TotalVulnerabilities = 0;
req.end(function (res) {
if (res.error);
var resXXResult = res.body;
resolve(resXXResult);
});
});
}
var splitReqUrl = function(request) {
return new Promise(function(resolve,reject) {
resolve(request[1]);
//for(var i=0; i< request.length; i++) {
// resolve(request[i]);
//}
});
}
var getRestSonar = function(requestX) {
return new Promise(function(resolve,reject) {
var unirest = require("unirest");
var reqx = unirest("GET", "http://sonarqubexxx/api/projects");
var outputJson = {
table: []
};
reqx.end(function (res) {
if (res.error) throw new Error(res.error);
// console.log(res.body);
var result = res.body;
//var needle = req.params.csi_id;
var needle = requestX;
var queryArray = [];
for (var i=0;i<result.length;i++) {
if (result[i].nm.indexOf(needle) !== -1) {
console.log(result[i].k);
var queryUrl = "http://sonarxxx/api/resources?resource="+result[i].k+"&metrics=code_smells,bugs,vulnerabilities,ncloc,coverage,duplicated_lines&format=json"
//console.log(queryUrl);
queryArray.push(queryUrl);
}
if (i === (result.length - 1)) {
resolve(queryArray);
}
}
});
});
}
Problem
First of all the problem with your solution is that you're trying to make everything inside a single big new Promise(...) creator.
Even if you manage to make that work it's still a common anti-pattern as Promises are made to be chained using the .then(...) method.
As pointed out by Roamer-1888 there oughta be a fork of unirest that handles Promises directly instead of requiring callbacks as in your example, but let's stick with your version of unirest here.
Solution
So what you need to be doing is create a Promise chain to handle the different steps of your code and pass the results down the chain.
Your steps seem to be:
Make the first call to retrieve initial results.
Filter the results based on the requestX input.
For each item left, make several calls to obtain more data.
Put everything back into an outputJson object.
Basically the only async steps are 1 and 3, but it might be ok to add a third step to build your outputJson and pass it downstream.
So let's start with the first step.
1. Make the first call
In the first link of the Promise chain we need to retrieve the initial results with your first unirest call:
new Promise((resolve, reject) => {
unirest("GET", "http://sonarqubexxServer/api/projects")
.end((res) => {
if (res.error) {
reject(res.error);
} else {
resolve(res.body);
}
});
})
See in this example I already checked if the response contains an error and fired a rejection in that case, otherwise I resolve the promise with the body (the data we need).
The Promise we created above will throw an error if the request fails, and will downstream the body of the response if everything goes fine.
2. Filtering and Sub-calls
Now then we can go ahead and use the full potential of Promises with the .then(...) method:
new Promise((resolve, reject) => {
unirest("GET", "http://sonarqubexxServer/api/projects")
.end((res) => {
if (res.error) {
reject(res.error);
} else {
resolve(res.body);
}
});
}).then((results) => {
results = results.filter((result) => {
return result.nm.indexOf(request) != -1;
});
return Promise.all(results.map((result) => {
return new Promise((resolve, reject) => {
var queryUrl = "http://sonarqubexxServer/api/resources?resource=" + result.k + "&metrics=code_smells,bugs,vulnerabilities,ncloc,coverage,duplicated_lines&format=json"
unirest("GET", queryUrl)
.end((res) => {
if (res.error) {
reject(res.error);
} else {
resolve(res.body);
}
});
})
}))
})
In this step I used some Array methods to make the code cleaner and Promise.all to handle several promises together.
Array.filter is a method which iterates an array and checks for each item if it should be kept in the filtered output or not. So, in your case, we want to keep only those items where result.nm.indexOf(request) != -1.
Array.map is a method which iterates an array and converts each item to something else. Basically the function you provide takes each item as input, converts it to something else and then replaces this new value to the old one in the output array.
Finally Promise.all accepts an array of Promises and returns a Promise itself. This returned Promise will resolve when all the given Promises resolve and will pass downstream an array which items are the results of each single Promise.
So by writing Promise.all(results.map((results) => { return new Promise(...) })) we convert each result in the results array into a Promise that executes the result-specific call and put it into the output array of Promises which is fed to Promise.all so they get executed at once.
3. Build the outputJSON
Now the Promise chain outputs the result of Promise.all which is an array of all the results of each Promise, which are the results of each sub-call.
We can then simply take the downstream data and use your nested iterations to build the outputJSON to be passed downstream:
new Promise((resolve, reject) => {
unirest("GET", "http://sonarqubexxServer/api/projects")
.end((res) => {
if (res.error) {
reject(res.error);
} else {
resolve(res.body);
}
});
}).then((results) => {
results = results.filter((result) => {
return result.nm.indexOf(request) != -1;
});
return Promise.all(results.map((result) => {
return new Promise((resolve, reject) => {
var queryUrl = "http://sonarqubexxServer/api/resources?resource=" + result.k + "&metrics=code_smells,bugs,vulnerabilities,ncloc,coverage,duplicated_lines&format=json"
unirest("GET", queryUrl)
.end((res) => {
if (res.error) {
reject(res.error);
} else {
resolve(res.body);
}
});
})
}))
}).then((allResults) => {
var TotalDuplicateLines = 0;
var TotalBugs = 0;
var TotalNcloc = 0;
var TotalCodeSmells = 0;
var TotalVulnerabilities = 0;
var outputJson = {
table: []
};
for (var i = 0; i < allResults; i++) {
for (var j = 0; j < allResults[i].length; j++) {
allResults[i][j].msr.forEach(m => {
if (m.key === 'duplicated_lines') {
TotalDuplicateLines += m.val;
}
else if (m.key === 'bugs') {
TotalBugs += m.val;
}
else if (m.key === 'ncloc') {
TotalNcloc += m.val;
}
else if (m.key === 'code_smells') {
TotalCodeSmells += m.val;
}
else if (m.key === 'vulnerabilities') {
TotalVulnerabilities += m.val;
outputJson.table.push({ totduplines: TotalDuplicateLines }, { totVul: TotalVulnerabilities });
}
});
}
}
return outputJson;
})
If your return this long Promise chain in your getRestSonar(request) function, then you could write getRestSonar(request).then((outputJson) => { ... do something with your outputJson ... })
I think that I am an idiot. What is happening here?? Why there is no i:0 or i:1, just only the last one? It shows that it loops everything and just after looping it tries to save and it is saving the same last object many time and after that I'll get error 500, duplicate key in DB. Is it even possible to save objects inside the for loop :) in AngularJS?
In console.log:
reasonList.length: 2
rma.js: 284 i: 2
rma.js: 285 defectdescDefectdescId: 2
rma.js: 286 returnreasonId: 1
rma.js: 287 rmaId: 15
code:
savedRma = rmaService.save({}, rma);
savedRma.$promise.then(function (result) {
$scope.rma = result;
console.log('result.rmaID--------->' + result.rmaId);
saveReturnReason(result.rmaId);
}, function (error) {
alert('Error in saving rma' + error);
});
$location.path('/rma-preview/' + $scope.rma.rmaId);
rmaDataService.setRma($scope.rma);
}
}; // ELSE CREATE RMA END
function saveReturnReason(rmaId) {
for (var i = 0; i < $scope.reasonList.length; i++) {
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.defectdescDefectdescId = $scope.reasonList[i].defectdescId;
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.returnreasonId = $scope.reasonList[i].returnreasonReturnreasonId.returnreasonId;
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.rmaId = rmaId;
savedRmaHasDefectdesc = rmaDefectSvc.save({}, $scope.rmaHasDefectdesc);
savedRmaHasDefectdesc.$promise.then(function (response) {
$scope.savedRmaHasDefectdesc = response;
console.log('i: ' + i)
console.log('defectdescDefectdescId:' + response.rmaHasDefectdescPK.defectdescDefectdescId);
console.log('returnreasonId:' + response.rmaHasDefectdescPK.returnreasonId);
console.log('rmaId:' + response.rmaHasDefectdescPK.rmaId);
}, function (error) {
alert('Error in saving reasons' + error);
});
} // For loop ending
};
UPDATE FOR forEach
I updated for loop to forEach. Same result, no luck. Still not going to promise.then in first each and then tries to save the last reason multiple times.
function saveReturnReason(rmaId) {
$scope.reasonList.forEach(function(reason){
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.defectdescDefectdescId = reason.defectdescId;
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.returnreasonId = reason.returnreasonReturnreasonId.returnreasonId;
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.rmaId = rmaId;
console.log('rmaId: ' +rmaId+': returnReasonId: ' +reason.returnreasonReturnreasonId.returnreasonId +' defectID: '+reason.defectdescId);
savedRmaHasDefectdesc = rmaDefectSvc.save({}, $scope.rmaHasDefectdesc);
// At the first loop, never comes to .then
savedRmaHasDefectdesc.$promise.then(function (response) {
$scope.savedRmaHasDefectdesc = response;
}, function (error) {
alert('Error in saving reasons' + error.status);
});
});// ForEach ending
};
Scope the i so that it's only available in the loop:
for (var i = 0; i < $scope.reasonList.length; i++) {
(function(i){
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.defectdescDefectdescId = $scope.reasonList[i].defectdescId;
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.returnreasonId = $scope.reasonList[i].returnreasonReturnreasonId.returnreasonId;
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.rmaId = rmaId;
savedRmaHasDefectdesc = rmaDefectSvc.save({}, $scope.rmaHasDefectdesc);
savedRmaHasDefectdesc.$promise.then(function (response) {
$scope.savedRmaHasDefectdesc = response;
console.log('i: ' + i)
console.log('defectdescDefectdescId:' + response.rmaHasDefectdescPK.defectdescDefectdescId);
console.log('returnreasonId:' + response.rmaHasDefectdescPK.returnreasonId);
console.log('rmaId:' + response.rmaHasDefectdescPK.rmaId);
}, function (error) {
alert('Error in saving reasons' + error);
});
})(i)
} // For loop ending
The problem as other people have mentioned is that your loop finishes before the promise is done, so by the time you console log the i is already updated.