Chained promises in AngularJS - javascript

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.

Related

Uncaught (in promise):How to await a function which returns a promise inside a loop?

I am working on awaiting a function that returns a promise inside a loop. But, I was not able to resolve it.
the loop is
for (var i = 0; i < rows.length; i++) {
console.log("row " + i);
getData(i).then(function (data) {
console.log("data of row "+ i +" is "+ data);
});
}
The output is:
row 0
row 1
row 2
data of row undefined data
data of row undefined data
data of row undefined data
i've tried to put the loop in a Async Context and await for the getData function but it causes deadlock with the error of Uncaught (in promise) undefined at row 0 like below:
getRows().then(async function (rows) {
for (var i = 0; i < rows.length; i++) {
console.log("row " + i);
await getData(i).then(function (data) {
console.log("data of row "+ i +" is "+ data);
});
}
});
Of worth to say, i'v defined promise reject function like this:reject(undefined) but i'v tried reject(false) and reject(null) also. However, No difference and Uncaught (in promise) exception throws and deadlock happens.
function getData(row) {
var promise = new Promise(function (resolve, reject) {
whenAvailable("read", function (t) {
read("data", row, 0, 1, true, "so").then(function (data) {
if (data != undefined) {
resolve(data);
} else {
reject(false);
}
});
});
});
return promise;
}
and also i'v check other stackoverflow related question but they were not suitable for my problem.
Can you help me overcome this problem?
Update
i did below as #SunilLama said
getRows().then(async function (rows) {
if (rows != undefined) {
for (var i = 0; i < rows.length; i++) {
console.log("rows " + i);
await getData(i).then(function (data) {
console.log(data);
}, error => console.log(error));
}
}
again deadlock with exception of
wrapping the loop in an async function should do it:
function getData(i) {
var promise= new Promise((resolve, reject) => {
setTimeout(function() {
resolve(i+1);
},1000);
});
return promise;
}
var main = async() => {
for (var i = 0; i < 10; i++) {
console.log("row " + i);
await getData(i).then(function(data) {
console.log("data of row " + i + " is " + data);
},error=> console.log(error));
}
}
main();
If your point is that each iteration should wait previous one, then one way is to do it with recursion.
var rows = ["a","b","c"];
function getData(i) {
var promise= new Promise((resolve, reject) => {
setTimeout(function() {
resolve(rows[i]);
},1000);
});
return promise;
}
function recur(i) {
i = i || 0; // For first time, makes it zero
if (i >= rows.length) { return; } // Stop if it's out of range
console.log("row " + i);
getData(i).then(function (data) {
console.log("data of row "+ i +" is "+ data);
recur(i + 1); // Call for next iteration
});
}
recur(); // Call

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.

Why is my for loop containing a fetch API call only running once?

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.

Java script for loop Parse Promise

I am trying to read a file on parse.com and using a for loop iterate over all the records present in it. On each record, I need to perform 4 operations, each dependent on the other. Can someone please guide how I can do that so that each record is processed in the for loop.
Parse.Cloud.httpRequest({
url: urlValue
}).then(function(fileResponse) {
console.log("processUploadFile:httpRequest:response:" + JSON.stringify(fileResponse.buffer.length));
// console.log("processUploadFile:Text:" + fileResponse.text);
var records = fileResponse.text.split("\r");
for (var i = 0; i < records.length; ++i) {
// console.log("Record:" + i + " detail:" + records[i] + "\n\n");
var record = records[i];
console.log("processUploadFile:adding patient");
Parse.Cloud.run("addPatient", {
record:record
}, {
success: function(objectId) {
console.log("Created objectId:" + JSON.stringify(objectId));
Parse.Cloud.run("addProvider", {
record:record
}, {
success: function(objectId) {
console.log("Created objectId:" + JSON.stringify(objectId));
Parse.Cloud.run("addLocation", {
record:record
}, {
success: function(objectId) {
console.log("objectId:" + JSON.stringify(objectId));
},
error: function(error) {
console.log(JSON.stringify(error));
}
});
},
error: function(error) {
console.log(JSON.stringify(error));
}
});
},
error: function(error) {
console.log(JSON.stringify(error));
}
});
};
}
}
response.success();
});
The right right answer depends on the semantics of those operations, whether they depend on each other in any way. The other part of a right right answer accounts for transaction rate limits and timeouts imposed by parse.com. That also depends on what happens in the cloud operations and how much data is being processed.
But the right answer (as opposed to right right) is to perform operations serially by chaining promises' then(), and to perform groups of operations concurrently (or in arbitrary order) with Parse.Promise.when().
One such ordering would look like this:
var patientQs = [];
var providerQs = [];
var locationQs = [];
var records;
Parse.Cloud.httpRequest({url: urlValue}).then(function(fileResponse) {
console.log("processUploadFile:httpRequest:response:" + JSON.stringify(fileResponse.buffer.length));
records = fileResponse.text.split("\r");
for (var i = 0; i < records.length; ++i) {
// console.log("Record:" + i + " detail:" + records[i] + "\n\n");
var record = records[i];
patientQs.push(Parse.Cloud.run("addPatient", {record:record}));
providerQs.push(Parse.Cloud.run("addProvider", {record:record}));
locationQs.push(Parse.Cloud.run("addLocation", {record:record}));
}
return Parse.Promise.when(patientQs);
}).then(function() {
// since the result of addPatient is an objectId, arguments
// will be the corresponding objectIds for each run
for (var i=0; i<arguments.length; i++) {
console.log(arguments[i] + " is the object id for input record " + JSON.stringify(records[i]));
}
return Parse.Promise.when(providerQs);
}).then(function() {
return Parse.Promise.when(locationQs);
}).then(function() {
response.success();
}, function(error) {
response.error(error);
});
This says, "go thru the http-retrieved records, and first add all of the patients for those records, then add all of the providers, and so on".
Or, you could group the operations by input record, like this:
Parse.Cloud.httpRequest({url: urlValue}).then(function(fileResponse) {
console.log("processUploadFile:httpRequest:response:" + JSON.stringify(fileResponse.buffer.length));
var records = fileResponse.text.split("\r");
var recordQs = [];
for (var i = 0; i < records.length; ++i) {
// console.log("Record:" + i + " detail:" + records[i] + "\n\n");
var record = records[i];
recordQs.push(processARecord(record));
}
return Parse.Promise.when(recordQs);
}).then(function() {
response.success(arguments);
}, function(error) {
response.error(error);
});
function processARecord(record) {
var result = {};
return Parse.Cloud.run("addPatient", {record:record}).then(function(objectId) {
console.log(objectId + " is the object id for input record " + JSON.stringify(record));
result.patientId = objectId;
return Parse.Cloud.run("addProvider", {record:record});
}).then(function (providerId) {
result.providerId = providerId;
return Parse.Cloud.run("addLocation", {record:record});
}).then(function(locationId) {
result.locationId = locationId;
return result;
});
}

How to right use promises and $q to handle asynchronous calls in Angular?

I have code with following structure (pseudocode):
Page init (app starts):
a] getDateRange -> Get array of date range (for example array with 10 items)
b] getResultFromDatabaseForDateRange -> Assync task which return JSON object after each call
I would like to call method b in for each cycle and each returned JSON Array object store into class global variable. After receiving the all responses from b i would like to set whole filled object into scope.
Could somebody give me the example how can I do it in Angular right way?
Thanks for any advice.
I tried to do this by this way but it doesn't works (code is simplified because of length):
$scope.getDateRange = function(direction) {
console.log('Trying to set date range for ' +direction);
switch(direction) {
case 'today':
console.log("Trying to set date range for this week - "+countOfWeeksInPast+" weeks");
dateTo = moment().day(7).format('YYYY-MM-DD');
dateFrom = moment().day(1).format('YYYY-MM-DD');
console.log(dateTo);
console.log(dateFrom);
// CREATE THIS WEEK - 10 WEEKS RANGE IN PAST
for (var i = 0; i < countOfWeeksInPast; i++) {
var row = {};
if(i==0) {
row.ID = i;
row.DATE_TO = dateTo;
row.DATE_FROM = dateFrom;
dateRanges.push(row);
} else {
row.ID = i;
row.DATE_TO = dateTo = moment(dateTo).subtract(7, 'days').format('YYYY-MM-DD');
row.DATE_FROM = dateFrom = moment(dateFrom).subtract(7, 'days').format('YYYY-MM-DD');
dateRanges.push(row);
}
}
$scope.getResultFromDatabaseForDateRange('create_new').then(function(result){
// THIS GIVES THE VALUE:
//alert("Result is" + JSON.stringify(result));
console.log("Returned Result is: " + JSON.stringify(result));
//return result;
}, function(e){
$ionicLoading.show({
template: $translate.instant('ERROR_DATABASE'),
duration:1000
});
});
}
};
$scope.getResultFromDatabaseForDateRange = function(listAction) {
console.log('trying to get data for selected date ranges');
// SHOW LOADING MESSAGE
$ionicLoading.show({
template: 'Loading data'
});
var deferred = $q.defer();
// INSTANTIATE DB CONNECTION
db = window.sqlitePlugin.openDatabase({name:"callplanner"});
var ic=0;
for(ic; ic < dateRanges.length; ic++) {
var sqlQuery =
"SELECT '"+dateRanges[ic].DATE_FROM+"' as DATE_FROM, "+
" '"+dateRanges[ic].DATE_TO+"' as DATE_TO, "+
" COUNT(*) AS DIALS_CNT, "+
" SUM(CASE WHEN dc.call_result = '"+CALL_RESULT_STATE_APPT+"' THEN 1 ELSE 0 END) AS '"+APPT_CNT+"', "+
" SUM(CASE WHEN dc.call_result = '"+CALL_RESULT_STATE_CONV_NO_APPT+"' THEN 1 ELSE 0 END) AS '"+CONVERS_CNT+"' , "+
" SUM(CASE WHEN dc.call_result = '"+CALL_RESULT_STATE_CANNOT_REACH+"' THEN 1 ELSE 0 END) AS '"+CANNOT_REACH_CNT+"' "+
" FROM "+DIALED_CALLS_TABLE+" dc "+
" WHERE dc.date BETWEEN '"+dateRanges[ic].DATE_FROM+"' AND '"+dateRanges[ic].DATE_TO+"';";
console.log(sqlQuery);
db.transaction(function(tx) {
// init empty array for results
tx.executeSql(sqlQuery, [], function(tx,results){
for (var i=0; i < results.rows.length; i++){
row = results.rows.item(i);
//Udpate date for writeout
//row.DATE = moment(row.DATE).format('ddd DD.M');
//row.SUCCES_RATE = DialsCompute.computeSuccessRateDaily(row);
listData.push(row);
console.log("row is " + JSON.stringify(row));
}
console.log(JSON.stringify(listData));
});
},function (e) {
console.log("ERROR: " + e.message);
deferred.reject(e);
});
}
deferred.resolve(row);
$ionicLoading.hide();
return deferred.promise;
};
You need to count the number of rows processed, and when it is equal to number of rows needs to be read, you should resolve your promise. So in your case:
var numberOfProcessed = 0;
for(ic; ic < dateRanges.length; ic++) {
var sqlQuery ="..."
db.transaction(function(tx) {
// init empty array for results
tx.executeSql(sqlQuery, [], function(tx,results){
//process your result from sql
numberOfProcessed++;
if(numberOfProcessed == dateRanges.length){
deferred.resolve(resultObject); // resolve your promise when you are sure you handled everything
}
console.log(JSON.stringify(listData));
});
},function (e) {
console.log("ERROR: " + e.message);
deferred.reject(e);
});
}
And btw getResultFromDatabaseForDateRange needs to be part of a service, business logic, or connection to backend services etc. should not be part of controllers.
For my news ticker directive I have the following created:
//method call
fetchData($scope.verbs).then(function (messageModel) {
$scope.messageModel = messageModel;
$scope.toggleTimer();
}, function (data) {
$element.css("display", "none");
$log.error("error: " + data.message);
});
//helper functions
function fetchData(verbs) {
return checkRequiredVerbs(verbs)
.then(getDataFromSource)
.then(prepareMessages);
};
function checkRequiredVerbs(verbs) {
var deferred = $q.defer(),
arrRequiredVerbs = configManager.baseUrl.split(/\/:/g);
if (angular.isObject(verbs)) {
for (var i = 1; i < arrRequiredVerbs.length; i++) {
if (!verbs.hasOwnProperty(arrRequiredVerbs[i])) {
deferred.reject({message: "Verb '" + arrRequiredVerbs[i] + "' is unknown"});
break;
}
}
deferred.resolve(verbs);
} else {
deferred.reject({message: "Wrong verb syntax!"});
}
return deferred.promise;
};
function getDataFromSource(verbs) {
return messageLoaderFactory.query(verbs).$promise;
};
function prepareMessages(messageModel) {
var deferred = $q.defer(),
tmpMessageModel = [];
for (var i = 0; i < messageModel.length; i++) {
messageModel[i].visible = (i === 0);
}
deferred.resolve(messageModel);
return deferred.promise;
};
}}
In my case before fetching the data from service I have to check the input params. At first the function fetchData will be called. Inside this function thecheckRequiredVerbs function will be called and returns an Promise. This promise will be transfer to the next function getDataFromSource and returns an another Promise. Lastly the function prepareMessages will be called. When something breaks all actions stop and the news ticker will be hidden.
The full project can you find here

Categories

Resources