I am working on function that calculates a distance from a given object to a number of other places with the help of Google Distance Matrix which is of course async therefore I'm dealing with promises.
When the number of places is one, everything works great. But once I have more than one promise, $q.all doesn't do anything : it neither resolves in a success nor in error. Though I have checked in the console that the calls to the Google distance matrix do happen and return a correct result. Any clue what can be at play here?
I am using AngularJS 1.6.4. Let me know should you need any more details. Thanks!
var requests = [];
for (var i = 0; i < ctrl.places.length; i += 1) {
var deferred = $q.defer();
requests.push(deferred.promise);
var destination = ctrl.places[i].latLng;
service.getDistanceMatrix({
origins: [ctrl.origin],
destinations: [destination[0] + "," + destination[1]],
travelMode: 'DRIVING'
}, function(response, status) {
if (status === 'OK') {
deferred.resolve(response.rows[0].elements[0].distance.text);
}
});
}
$q.all(requests).then(function(result) {
ctrl.distances = result;
});
Your problem is that var is not block-scoped, so the value of deferred will always belong to the final iteration of your loop by the time any of your callbacks are invoked. A consequence of this will that the earlier deferred will never be resolved and the $q.all will appear to hang.
The simplest way to resolve this is to change your use of var to let to take advantage of block scoping:
let deferred = $q.defer();
Reason why it does not work
By the time the service call resolves and the callback handler is invoked, the deferred attribute is referring to the very last deferred object created by the for loop. So, in effect, you are always performing a resolve on the very last deferred object that was created.
Solution:
Create a new function:
function getDistanceMatrixForDestination (destination, origins) {
var deferred = $q.defer();
service.getDistanceMatrix({
origins: [origins],
destinations: [destination[0] + "," + destination[1]],
travelMode: 'DRIVING'
}, function(response, status) {
if (status === 'OK') {
deferred.resolve(response.rows[0].elements[0].distance.text);
} else {
deferred.reject();
}
});
return deferred.promise;
}
Change your existing code to this:
var requests = [];
for (var i = 0; i < ctrl.places.length; i += 1) {
var destination = ctrl.places[i].latLng;
requests.push(getDistanceMatrixForDestination (destination, ctrl.origins));
}
$q.all(requests).then(function(result) {
ctrl.distances = result;
});
Related
///Returning JSON 1
$http.get("url1").
then(function (response) {
$scope.foo = response.data;
});
///Returning JSON 2
$http.get("url2").
then(function (response) {
$scope.foo = response.data;
});
///Returning JSON (n)
$http.get("n").
then(function (response) {
$scope.foo = response.data;
});
Can I somehow concat these JSON objects into one? The reason is that I have ALOT of data and since I rather would like to display alot of data for the user to filter through than to have them click through 1000 pages in a SPA, I would like to join them if that's possible (in a reasonable manner ofcourse).
EDIT
I was thinking something like this
var url ="";
for (... i < 100...) {
url = "http://url.com"+i+"";
$http.get(url).
then(function(response){
$scope.foo.concat(response.data);
}
);
}
Update
I've managed to join the JSON returns into an array of objects. But the problem is that this array now contains objects which in itself contains an object which in itself contains an array of objects... yup!
If it's array then you can concat it.
Initialize empty array first
$scope.foo = [];
$http.get("url1").
then(function (response) {
$scope.foo.concat(response.data);
});
Use $q.all to create a promise that returns an array:
function arrayPromise(url, max)
var urlArray = [];
for (let i=0; i<max; i++) {
urlArray.push(url + i);
};
var promiseArray = [];
for (let i=0; i<urlArray.length; i++) {
promiseArray.push($http.get(urlArray[i]);
};
return $q.all(promiseArray);
});
To fetch nested arrays, chain from the parent:
function nestedPromise (url, max) {
var p1 = arrayPromise(url + "item/", max);
var p2 = p1.then(function(itemArray) {
var promises = [];
for (let i=0; i<itemArray.length; i++) {
var subUrl = url + "item/" + i + "/subItem/";
promises[i] = arrayPromise(subUrl, itemArray[i].length);
};
return $q.all(promises);
});
return p2;
};
Finally resolve the nested promise:
nestedPromise("https://example.com/", 10)
.then(function (nestedArray) {
$scope.data = nestedArray;
});
It is important to use a return statement at all levels of the hierarchy: in the .then methods and in the functions themselves.
Chaining promises
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises.
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.
— AngularJS $q Service API Reference - Chaining Promises
You can wait for the n requests to finish and then do whatever you want with the object returned.
$q.all($http.get("url1"), $http.get("url2"), $http.get("url3"))
.then(function (responses) {
// This function is called when the three requests return.
// responses is an array with the first item being the result of
// fetching url1, the second fetching url2, etc.
// Depending on what the response looks like you may want to do
// something like:
$scope.data = angular.merge(responses[0], responses[1] /* etc */);
});
My code:
$q(function (resolve) {
var imageUploadResults = [];
fileUploadService.uploadImage($scope.filesUpload, "/api/mailbox/uploadAttachFile", function (result) {
console.log(result);
imageUploadResults.push(result.LocalFilePath);
});
$scope.mail.Files = imageUploadResults;
resolve($scope.mail);
}).then(function (mail) {
console.log(mail);
apiService.post("/api/mailbox/sendMail", mail, sendMailSucceed, sendMailFailed);
});
Expect:
I want to add value to mail.Files finish,then call apiService.post()
Actual:
But it execute apiService.post() with mail.Files value is [].
When apiService.post() execute finish mail.Files return value.length > 0.
Without knowing exactly which library you are actually using, it seems clear to me that fileUploadService.uploadImage() is asynchronous.
The function that you give as an argument is a callback and there is no guarantee that it would be executed "on time". In your case the path is added to imageUploadResults after the moment where you set $scope.mail.Files.
you should set $scope.mail.Files and call resolve in your callback function.
$q(function (resolve) {
var imageUploadResults = [];
fileUploadService.uploadImage($scope.filesUpload, "/api/mailbox/uploadAttachFile", function (result) {
console.log(result);
imageUploadResults.push(result.LocalFilePath);
$scope.mail.Files = imageUploadResults;
resolve($scope.mail);
});
}).then(function (mail) {
console.log(mail);
apiService.post("/api/mailbox/sendMail", mail, sendMailSucceed, sendMailFailed);
});
When you assigned $scope.mail.Files = imageUploadResults; and resolved resolve($scope.mail); there are no guarantee that fileUploadService.uploadImage finished request and saved imageUploadResults.push(result.LocalFilePath);
Possible solution is to add resolve($scope.mail); right after imageUploadResults.push(result.LocalFilePath); in function passed to fileUploadService.uploadImage
Your .then function should be using $scope.mail and not mail as a parameter? so as of right now you're sending an empty object through instead of the mail object.
Problem description
Im using the angular resource to get data from my server. I've extended it a bit to make sure all of my resources have security headers.
Problem is that on the second get request and on, my get requests are sent with limit=0, and only the first get request is sent correctly (with limit=12).
Code part
This is my base resource factory (for making sure all resource contain the keys and everything):
app.factory('SecuredFactory', function($resource){
var DEFAULT_ACTIONS = {
'get': {method:'GET'},
'query': {method:'GET', isArray:true},
};
var DEFAULT_PARAMS = {
'limit': 12,
'format': 'json'
};
for(var key in DEFAULT_ACTIONS){
DEFAULT_ACTIONS[key]['headers'] = <headers object>;
}
var securedResource = function(url, paramDefaults, actions){
for (var attrname in actions) {
DEFAULT_ACTIONS[attrname] = actions[attrname];
}
for (var attrname in paramDefaults) {
DEFAULT_PARAMS[attrname] = paramDefaults[attrname];
}
var defaultResource = $resource(url, DEFAULT_PARAMS, DEFAULT_ACTIONS);
return defaultResource;
};
return securedResource;
});
And this is an example of how I creat a specific factory out of the secured one:
app.factory('QuestionFactory', function(SecuredFactory, Constants){
var url = Constants.SERVER_URL + 'question/';
var Task = SecuredFactory(url);
return Task;
});
And this is finally how I use it, for example:
// filtering example (not important for this matter):
var filtering = {author: "Daniel"};
var contents = [];
var resource = QuestionFactory;
resource.get(filtering, function (res) {
// success fetching
$scope.contents = $scope.contents.concat(res['objects']);
}
// failed fetching
, function (err) {
}
);
The requests
first request:
question?format=json&limit=12&offset=0
second request and on:
question?format=json&limit=0&offset=0
My problem was that the DEFAULT_PARAMS variable was declared as global. I didn't realize that invoking the secured factory with {limit: 0} will override the global, therefore changing the limit to 0 for ALL of my resources.
Changing the securedFactory to a service and moving the "globals" into the returned function solved it. Had to add new ofcourse before every securedService call.
I want to post images on server continuously and i am doing this by putting it in a loop of images length.I want to call the function again on the success of previous image upload using promises.
Below is the code i am using
$scope.questionimageuploadfun = function(surveyid, questionid, type, questions) {
angular.forEach(questions, function(value, key) {
$scope.upload = $upload.upload({
url: 'questions/' + questionid + '/options/' + value.id,
file: value.file,
fileFormDataName: 'myfile',
}).progress(function(evt) {
console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
}).success(function(data, status, headers, config) {
// file is uploaded successfully
if (!data.error && type == "new") {
toaster.pop('success', "Question", "Question added succesfully");
}
})
// });
}
I searched for the way to use promises but no success.I want to do this on success of every call till the condition gets satisfies
To create a controlled infinite loop with promises can be a bit tricky.
I structured a basic function here for you, but you need to modify it to work to your
$scope.questionImageUploadFun = function(surveyid,questionid,type,questions){
var defer = $q.defer(); // the deferred object, a promise of work to be done
$http.get('someUrl', parameters).then(function(response){
// do something with success response
defer.resolve(response);
}, function(reasonForFail){
// do something with failure response
defer.reject(reasonForFail);
});
return defer.promise; // return the promise
};
// define success function
var onSuccess = function(response){
// declare local parameters here
// call function again
$scope.questionImageUploadFun().then(onSuccess); // success function should call itself again on the next success
};
var onFailure = function(reason){
// do something else?
};
var surveyId = 1;
var questionId = 1;
var type = 1;
var question = 1;
// call the function, since it returns a promise, you can tag a then() on the end of it, and run the success function.
$scope.questionImageUploadFun(surveyid,questionid,type,questions).then(onSuccess);
I have a function that checks for for records and if they exist it downloads them for each item. This is a function that happens with in a loop so there can me many records. I thought that I was using $Q properly to deffer each $http request to wait for one after each other so they do not all happen at the same time but they all fire at the same time still.
I have seen $q.defer(); but do not understand how to use it in my implementation. How would this be written properly deferring each call until the one before is complete?
CheckRecords: function(obj) {
var promise;
var promises = [];
if (obj.BD.img == 'checkedRecord') {
var objBDUrl = 'services/GetSurveyBD/?id=' + obj.BD.ID;
promise = $timeout(function(){
$http.get(objBDUrl, { cache: true }).then(function(response) {
obj.BD.ID = obj.BD.ID;
obj.BD.data = response.data;
});
}, 250);
promises.push(promise);
}
if (obj.MR.img == 'checkedRecord') {
var objMRUrl = 'services/GetMR/?id=' + obj.MR.ID;
promise = $timeout(function(){
$http.get(objMRUrl, { cache: true }).then(function(response) {
obj.MR.ID = obj.MR.ID;
obj.MR.data = response.data;
});
}, 250);
promises.push(promise);
}
$q.all(promises).then(function(){
return obj;
});
}
The function $q.all just ensures that all requests completed, the requests are still executed immediately, but their results are deferred. If you want to control the execution order you, do your requests in the result function.
$q
- service in module ng
A service that helps you run functions asynchronously, and use their return values (or exceptions) when they are done processing.