I have the following nested $http calls to two apis, problem I am facing is that I can't access outer $http call results from inner $http call even though outer $http call results is assigned to a separate variable. Can someone please tell me what I am missing here and how to fix it? Thanks
clientSvc.getInvoices(clientID).then(
function(clientInvoices) {
var invoiceID = '';
for (var i=0; i < clientInvoices.Result.length; i++) {
invoicesPromise.push(clientSvc.getAR(clientInvoices.Result[i].id).then(
function(ARList) {
//This will always return 3
console.log(i);
//following line raises an error id of undefined...
invoiceID = clientInvoices.Result[i].id;
},
function(status){
console.log(status);
}
));
}
$q.all(invoicesPromise).then(function() {
....
});
},
function(status){
console.log(status);
}
);
You reference i from the closure, but its value changes in the loop; when the then success functions do get called, i is clientInvoices.Result.length + 1, which is why clientInvoices.Result[i] is undefined. Use a separate function e.g. as:
for (var i=0; i < clientInvoices.Result.length; i++) {
invoicesPromise.push(callNested(clientInvoices.Result[i].id));
}
function callNested(resultId) {
return clientSvc.getAR(resultId).then(
function(ARList) {
invoiceID = resultId;
},
function(status){
console.log(status);
}
)
}
Still though, you are assigning to the single-valued variable invoiceID many times; this will cause problems. Also, there is no var invoicesPromise = []; in your code.
Related
I am unable to do the promise looping.
I make a service call to get list of providers, then for each provider, I make another service call to get a customer.
A provider has 1 or more customers. So eventual list of customer is to be decorated and displayed.
In other format I am trying to achieve:
*serviceA.getProvider(){
foreach(providers){
foreach(provider.customerID){
serviceB.getCustomer(customerId)
}
}
}
.then(
foreach(Customer){
updateTheCustomer;
addUpdatedCustomerToAList
}
displayUpdatedCustomreList();
)*
I have written following code, that isn't working
doTheJob(model: Object) {
let A = [];
let B = [];
let fetchP = function(obj) {
obj.Service1.fetchAllP().then(function (response) {
let P = cloneDeep(response.data);
_.forEach(P, function(prov) {
_.forEach(prov.CIds, function(Id) {
A.push(Id);
});
});
_.forEach(A, function(CId) {
return obj.Service2.getById(CId);//what works is if this statement was: return obj.Service2.getById(A[0]);
//So, clearly, returning promise inside loop isn't working
});
})
.then(function(response) {
B.push(response.data); //This response is undefined
angular.forEach(B, function (value) {
obj.updateAdr(value)
});
obj.dispay(B);
});
};
fetchP(this);
}
forEach don't stop when you use return inside of it, try to use a plain loop instead, why you don't just loop with for ?
_.forEach(A, function(CId) {
return obj.Service2.getById(CId);
}
as stated by #Ze Rubeus if you return inside a callback within a for loop that value will be lost, since it's not returned to the caller.
probably you wanted something like this
return Promise.all(A.map(function(CId){
//collect each promise inside an array that will then be resolved
return obj.Service2.getById(CId);
})
I have chainable promises which are working fine in a single series but now i want to call this serios of chain inside for loop but it does not work as expected.
see my demo plunker and see the output in console.
below is the structure of my chaining promises . I want all publicIP which is returned by funTwo; but I want to complete funThree() and then want to get all publicIP. As I know that $q.when() makes a value in promise object.
but you can see that console.log('pA', promiseArray); executed very before and console.log('res three'); and why successHandler and finally called before that?
Here surely I am missing something , may be have to write a return; in proper place , kindly help me how to executed all function in for loop and return a data array after that for loop ends which can be retried in successHandler
MyService.funZero()
.then(function(response) {
console.log(response);
var promiseArray = [];
for(var i = 0; i < 2 ; i++) {
console.log('I', i);
MyService.funOne()
.then(MyService.funTwo)
.then(function(res2) {
console.log('res two', res2);
publicIP = res2.ip;
console.log('i', publicIP);
promiseArray.push({'ip': publicIP});
return MyService.funThree(publicIP);
})
.then(function() {
console.log('res three');
})
} // for loop ends
console.log('pA', promiseArray);
return $q.when(promiseArray);
})
.then(function(res4){
console.log('after for loop', res4);
})
.then(successHandler)
.catch(errorHandler)
.finally(final, notify);
So, I'm not sure exactly what MyService.funThree does, but you can aggregate an array via ipArray.push({'ip': publicIP}) and return that to the MyService.funThree and then the subsequent function. The issue here is there is no guarantee of order in the ipArray if that's what you're looking for. Here's the middle section of that function:
ipArray = [];
for(var i = 0; i < 2 ; i++) {
console.log('I', i);
var promise = MyService.funOne()
.then(MyService.funTwo)
.then(function(res2) {
console.log('res two', res2);
publicIP = res2.ip;
console.log('i', publicIP);
ipArray.push({'ip': publicIP});
return ipArray;
})
.then(MyService.funThree)
.then(function() {
console.log('res three');
});
promiseArray.push(promise);
}
console.log('pA', promiseArray);
return $q.all(promiseArray);
I have this code in angular,
$http({
method:'POST',
url :'someURL', //returns an array of urls [url1, url2, url3..]
data : dataObj
})
.then(function(response) {
var items = response.data;
var promises = [];
$scope.output =[];
items.forEach(function(el){
return promises.push($http.get(el)); //fills the promise[] array
});
var ignore = function(x) { return x.catch(function(){}); } // To ignore if promise does not get resolved (only accept responses with status 200)
var all = $q.all( promises.map(ignore) ); //chaining promises array
all.then(function success(d){
console.log($scope.output); //want the output to be ["text1", "text2", "text3"...]
});
for (var i=0; i < promises.length ; i++){
promises[i].then(success).catch(function (){
});
function success(r){
$scope.output.push(r.data.text); //{text: "text1"}
}
}
});
The result of this operation is stored in $scope.output. On executing I'm getting output as ["text2", "text3", "text1" ...] which is not in a serial fashion. My question is how I can make this execute in a serial fashion so that the output would be ["text1", "text2", "text3" ...]
Replace your last for loop with the following:
angular.forEach(promises, function(promise, index){
promise.then(success).catch(function (){});
function success(r){
$scope.output[index] = r.data.text;
}
});
Due to closure paradigm the index variable will be available in the success handler upon promise resolution no matter in which order the promises get resolved and the results will be placed to the output array in the order of the original promises.
Didn't test it, but from first view I'd say you need to put the for() loop inside the all.then().
all.then(function success(d){
console.log($scope.output);
for (var i=0; i < promises.length ; i++) {
promises[i].then(success).catch(function () { });
function success (r) {
$scope.output.push(r.data.text);
}
}
});
Because otherwise you loop through partially unresolved promises. Those that resolve earlier will skip ahead in the queue.
Having the for() loop inside the all.then() you make sure that all promises have resolved already and will add themselves to the output list when they are called with promises[i].then(success).
IMO,you should not use callbacks inside for loop. I think, it is causing this behavior. I hope it will work. No need to add last for loop.
var all = $q.all( promises.map(ignore) );
all.then(function success(d){
d.forEach(function(res){
$scope.output.push(r.data.text);
});
console.log($scope.output);
});
There's a for loop and inside the for loop I'm calling an AJAX request. The issue I encountered is, the for loop finishes before the requests complete.
I want the for loop to continue to it's next iteration only after the required AJAX request completes.
PS- AJAX works fine. I do get my desired information from the server. It's just the for loop iterations complete first without waiting for the AJAX request success function to fire up. So when the AJAX success function finally fires the value in the variable cid is inconclusive as it has been overwritten by the last iteration of the for loop.
I want the for loop to continue only after the AJAX success function is executed.
Code:
if (window.cordova) {
db = $cordovaSQLite.openDB("my.db"); //device
} else {
db = window.openDatabase("my.db", '1', 'my', 1024 * 1024 * 100); // browser
}
var query = "SELECT * FROM order_product";
$cordovaSQLite.execute(db, query, []).then(function(res) {
if (res.rows.length > 0) {
for (var i = 0; i < res.rows.length; i++) {
console.log(" foreach SELECTED shopcart-> " + res.rows.item(i).id);
var cid = res.rows.item(i).coffee_id;
$http.post("http://192.168.1.4/coffeepayWeb/public/index.php/getsugar", {
cid: cID
})
.success(function(result) {
console.log("success");
if (cid == 6) {
//do something
} else {
//do something else
}
});
}
}
}
Using for is unsafe for asynchronous operations if you need the iteration index, use it only to store the required values to make the async operation ($http.post in this case), it should looks like:
var items = [];
for (var i = 0; i < res.rows.length; i++) {
var item = res.rows.item(i);
items.push(item);
}
after consider that $http returns a promise, then you should be able to map all elements from items
var getsugarUrl = 'http://192.168.1.4/coffeepayWeb/public/index.php/getsugar';
// Map the values to obtain the promises on each $http operation, the allSettled method of the
// [Kriskowal Q library](https://github.com/kriskowal/q/wiki/API-Reference) will be simulated
// this is because when one of the http requests fail then the $q.all method break and reject with an error
var promises = items.map(function (item) {
var cid = item.coffee_id;
return $http.post(getsugarUrl, { cid: cid })
.then(function (result) {
// You can take advantage of this closure to handle the result of the request an the
// cid property, store your modified result on the value property from the return
return {
state: 'fullfilled',
value: {
result: result,
cid: cid
} // your modified result
};
})
// Handle when the http request fails
.catch(function (err) {
return {
state: 'rejected',
error: err
};
});
});
finally handle the results obtained using $q.all (you need to inject the $q service)
$q.all(promises)
.then(function (responses) {
// iterate over the results
responses
.filter(function(response) { // only fullfilled results
return response.state == 'fullfilled';
})
.forEach(function (response) {
if (response.value.cid == 6) {
//do something with response.value.result
} else {
//do something else
}
});
});
With this solution the http requests aren't resolved sequentially, but you have control over when they've finished together and you will have the correct value of cid
Check more about JavaScript Promises
$http uses promises, which means you need to think of the problem within the promise paradigm.
Consider a recursive option where you pass in an array of your cID's, and each call sends a $http.post for the 1st cID in the array; if the call succeeded, we continue recursively with a smaller array, until there are no more left.
One promise is created & returned in the 1st call, which is notified on each successful query (allowing you to do your per-cID logic), and finally resolved when all queries are done (or rejected if any query fails).
// This function is called without deferred;
// deferred is used on recursive calls inside the function
function doPost(url, cidList, deferred) {
if (deferred === undefined) {
deferred = $q.defer();
}
var cid = cidList[0];
$http.post(url, {cid: cid})
.success(function(result) {
// query succeeded; notify the promise
deferred.notify({cid: cid, result: result});
if (cidList.length > 1) {
// there are more items to process; make a recursive
// call with cidList[1:end]
doPost(url, cidList.slice(1), deferred);
} else {
// we're done; resolve the promise
deferred.resolve();
}
})
.error(function(message) {
// there was an error; reject the promise
deferred.reject({cid: cid, message: message});
});
return deferred.promise;
}
// build the list of cIDs to pass into doPost
var cidList = [];
for (var i = 0; i < res.rows.length; i++) {
cidList.push(res.rows.item(i).coffee_id);
}
// start the queries
doPost("http://192.168.1.4/coffeepayWeb/public/index.php/getsugar", cidList)
.then(function() {
// promise resolved
console.log("All done!");
}, function(info) {
// promise rejected
console.log("Failed on cID " + info.cid + ": " + info.message);
}, function(info) {
// promise being notified
console.log("Just did cID " + info.cid + ": " + info.result);
// your per-cid handler
if (info.cid == 6) {
// do something
} else {
// do something else
}
});
UPDATE
Since the motivation for the question had more to do with variable scope (rather than sequential HTTP requests), this is all you really need:
// Build the CID list so that we can iterate it
var cidList = [];
for (var i = 0; i < res.rows.length; i++) {
cidList.push(res.rows.item(i).coffee_id);
}
// Iterate the list & call $http.post
cidList.forEach(function(cid) {
// your $http.post() logic; each call will have its own
// cid thanks to closures
});
Each iteration will have it's own cid, which you can use in your .success() or .error() handlers without worrying about it being overwritten. As with the other solution, the requests aren't sequential, but you probably didn't need them to be in the first place.
I have a need to make multiple concurrent calls to an Angular resource, and chain some actions with the $promise api.
I define a resource like this
myServicesModule.factory('MyResource', ['$resource', 'SETTINGS', function($resource, SETTINGS) {
return $resource(SETTINGS.serverUrl + '/myResource/:id', { },
{
get: { method: "get", url: SETTINGS.serverUrl + '/myResource/show/:id' },
}
);
}]);
My controller needs to retrieve multiple records, and take actions on each one when the record is ready. I am having trouble passing values to the then() closure.
When I do this:
for (var i = 0; i < 3; i++) {
MyResource.get({id: i}).$promise.then(function(item) { console.log(i); });
}
The output is "2, 2, 2".
This code results in the desired output of "0, 1, 2" (order varies depending on when each resource call completes), but this is an ugly solution.
for (var i = 0; i < 3; i++) {
var closure = function(i) {
return function(item) { console.log(i); console.log(item); }
}
UwgCarrier.get({id: i}).$promise.then( closure(i) );
}
Why does the first code snippet return "2, 2, 2" ?
Is there a cleaner way to solve this problem?
It's a matter of closures. Just wrap your closure in another one.
You can make a workaround with a call to an immediate function, that would look like :
for (var i = 0; i < 3; i++) {
(function(c) {
UwgCarrier.get({id: c}).$promise.then( console.log(c); );
})(i);
}
In my example I have replaced "i" by "c" into the closure to make things clear. Like so, the immediate function invoke "i" with its current value within the loop process.
I don't think that there is a better way to do this as it's a javascript concern.
EDIT :
As ES6 is coming soon, you can use the "let" keyword to achieve the same goal without wrapping your inner loop in a closure, because "let" block-scoped your variable.
for (let i = 0; i < 3; i++) {
UwgCarrier.get({id: i}).$promise.then( console.log(i); );
}