I have a very large angularjs app, that sells stuff and has filters
It seems that we need to support people on flaky connection.
That means that if user selects 'used product' filter and then he unselects 'used product', there will be a 2 calls to the server via $http.
$http.get("reloadresults?used=true", function (response) { $scope.items = response items; }); at 12:03:04 hours
$http.get("reloadresults?used=false", function (response) { $scope.items = response items; }); at 12:03:05
Now, image there is a bottleneck or something and the first call with 'used=true' returns last, then there is a problem with the filters.
I know there is a $http interceptor in angularjs, based on promises, how would i fix this problem? So that requests are processed in the order they are sent, meaning 'used=true' and only then used=false.
Edit: cant block thread, cant refactor, just need for the promises to fullfil in the order they were first sent. I think ill post answer later.
I din't understand your question well but i think you are looking for
$q.all(valid_request)
You could indeed ensure that success handlers are called in the correct order by forming a queue (a promise chain) however it is simpler, and better in this case, to nullify the previous request each time a new request is made.
There's a number of ways in which this could be achieved. Here's one ...
function cancelPrevious(fn) {
var reject_ = null;
return function(x) {
if(reject_) reject_(new Error('cancelled'));
return Promise.race(fn(x), new Promise(function(_, reject) {
reject_ = reject; // if reject_ is called before fn(x) settles, the returned promise will be rejected.
}));
};
}
which allows you to write ...
var get = cancelPrevious(function(str) {
return $http.get(str);
});
... and to make requests from any number of event threads :
get('reloadresults?used=true').then(function(response) {
// This function will be reached only if
// $http.get('reloadresults?used=true') fulfills
// before another get() call is made.
$scope.items = response.items;
});
...
// This call causes the then callback above to be suppressed if not already called
get('reloadresults?used=false').then(function(response) {
$scope.items = response.items;
});
Notes :
http requests are not canceled. Instead, the succuss path stemming from each request is made "cancelable" by racing against a rejectable promise.
side-effects included in the function passed to cancelPrevious() may be executed; in general, don't include such side effects.
Related
Angularjs app here.
There are 2 controllers that do similar things.
In particular, they have an interval. Each 10 seconds they go to their own service.
These 2 different services also do similar things. Most important is that they go to an URL that looks like this:
example.com/fromTimestamp=2019-11-21T15:13:51.618Z
As the two controllers start more or less at the same time, in the example above they could generate something like:
controller/service 1: example.com/fromTimestamp=2019-11-21T15:13:51.618Z
controller/service 2: example.com/fromTimestamp=2019-11-21T15:13:52.898Z
This is because the parameter is created in the service with his line:
var timestamp = fromTimestamp ? '&fromTimestamp=' +fromTimestamp.toISOString() : '';
So maybe there will be a difference of some seconds. Or even only a difference of milliseconds.
I would like to make only one request, and share the data fetched from http between the two services.
The most natural approach would seem to be using cache.
What I could understand is that this call could make the trick:
return $http.get('example.com/fromTimestamp=2019-11-21T15:13:51.618Z', {cache: true});
But looking in the dev tools it is still making 2 requests to the server. I guess this is because they have 2 different urls?
If that is the problem, what could be another approach to this problem?
In my apps, when face with this problem, I use the $q provider and a promise object to suspend all calls to the same endpoint while a singleton promise is unresolved.
So, if the app makes two calls very close together, the second call will not be attempted until the promise created by the first call is resolved. Further, I check the parameters of the calls, and if they are the same, then the original promise object is returned for both requests. In your case, your parameters are always different because of the time stamp. In that case, you could compare the difference in time between the two calls, and if it is under a certain threshold in miliseconds, you can just return that first promise object. Something like this:
var promiseKeeper; //singleton variable in service
function(endpointName, dataAsJson) {
if (
angular.isDefined(promiseKeeper[endpointName].promise) &&
/*promiseKeeper[endpointName].dataAsJson == dataAsJson && */
lastRequestTime - currentRequestTime < 500
) {
return promiseKeeper[endpointName].promise;
} else {
deferred = $q.defer();
postRequest = $http.post(apiUrl, payload);
postRequest
.then(function(response) {
promiseKeeper[endpointName] = {};
if (params.special) {
deferred.resolve(response);
} else {
deferred.resolve(response.data.result);
}
})
.catch(function(errorResponse) {
promiseKeeper[endpointName] = {};
console.error("Error making API request");
deferred.reject(extractError(errorResponse));
});
promiseKeeper[endpointName].promise = deferred.promise;
promiseKeeper[endpointName].dataAsJson = dataAsJson;
return deferred.promise;
}
}
In my application, I am storing data in local storage and trigger async http post in the background. Once successfully posted, the posted data gets removed from local storage. When http post is in process, there may be more data added to local storage so I need to queue up the post and sequentially process it because, I need to wait for the local storage to be cleared from the successful posts. The task should be called recursively until there is data in local storage.
taskQueue: function () {
var deferred = $q.defer();
queue.push(deferred);
var promise = deferred.promise;
if (!saveInProgress) {
// get post data from storage
var promises = queue.map(function(){$http.post(<post url>, <post data>).then(function(result){
// clear successful data
deferred.resolve(result);
}, function(error){
deferred.reject(error);
})
})
return $q.all(promises);
}
As angular newbie, I am having problems with the above code not being sequential. How can I achieve what I intend to? The queue length is unknown and also the queue length increases as the process is in progress. Please point me to other answers if this is a duplicate.
Async.js sounds a good idea but if you don't want to use a library ...
$q.all will batch up an array of requests and fire them at the same time and will resolve when ALL promises in the array resolve - WHICH IS NOT WHAT YOU WANT.
to make $http calls SEQUENTIALLY from an array do this ....
var request0 = $http.get('/my/path0');
var request1 = $http.post('/my/path1', {data:'fred'});
var request2 = $http.get('/my/path2');
var requestArray = [];
then ...
requestArray.push(request0);
requestArray.push(request1);
requestArray.push(request2);
then ...
requestArray[0].then(function(response0) {
// do something with response0
return requestArray[1];
}).then(function(response1) {
// do something with response1
return requestArray[2];
}).then(function(response2) {
// do something with response2
}).catch(function(failedResponse) {
console.log("i will be displayed when a request fails (if ever)", failedResponse)
});
While having a library solution would be great (per #nstoitsev's answer), you can do this without it.
sequential requests of unknown length
Just to recap:
we do not know the number of requests
each response may enqueue another request
A few assumptions:
all requests will be working on a common data object (local storage in your case)
all requests are promises
running the queue
function postMyData (data){
return $http.post(<url>, data)
}
var rqsts = []
function executeQueue (){
if(!rqsts.length)
//we're done
return
var rqst = rqsts.shift()
rqst()
.then(function(rsp){
//based on how you're determining if you need to do another request...
if(keepGoing)
rqsts.push(postMyData(<more data>))
})
}
codepen - http://codepen.io/jusopi/pen/VaYRXR?editors=1010
I intentionally left this vague because I don't understand what the conditions for failure are and if you wanted to vary up the requests to use more than the same $http.post call, you could pass it back in some way.
and just a suggestion
As angular newbie...
Many things are progressing towards this whole functional, reactive programming paradigm. Since you're relatively new to Angular and NG2 already has some of this built in, it might be worthy of your attention. I think rxjs is already in many NG2 example bundles.
The easies way to achieve this is by using Async.js. There you can find a method called mapSeries. You can run it over the queue and it will sequentially process all elements of the array one by one, and will continue to the next element only when the correct callback is called.
I have been reading about $q and promises for days now and I seem to understand it...somewhat. I have the following situation in practice:
An $http request is made and checks whether a subsequent call can be made.
If the first call fails, return "no data", if it succeeds and says a call can be made, the second call is made, if not - "no data" again. If the second call succeeds, it returns data, if not - "no data". It looks like this (approximately, I simplified for general idea, so don't worry about the minor mistakes here):
return $http.get (something)
.then(function(allowedAccess){
if(allowedAccess){
return $http.get (somethingElse)
.then( function(result){return {data:result} },
function(error){return {data:"n0pe"} }
)
} else {
return {data:"n0pe"}
}
},
function(){ return {data:"n0pe"} });
I was told to use $q here. I don't really understand how or why I would. The $http calls are promises already.
If there is a way to make this cleaner, I don't see it. Just got done re-reading this post on the subject. Essentially, am I missing something / is there a better way to do this?
Edit: Also just re-read a tutorial on chaining promises - it doesn't handle call failures at all. Basically posting this as due diligence.
Edit 2: This is more of an elaborate on the theory I am asking about, excerpt from the first article:
This is a simple example though. It becomes really powerful if your then() callback returns another promise. In that case, the next then() will only be executed once that promise resolves. This pattern can be used for serial HTTP requests, for example (where a request depends on the result of a previous one):
This seems to be talking about chains like this:
asyncFn1(1)
.then(function(data){return asyncFn2(data)})
.then(function(data){return asyncFn3(data)})
So, if I understand correctly this a). Doesn't apply to me because I don't have a 3rd function. b). Would apply to me if I had three functions because while I run an if statement inside the first $http request, and only inside the if statement do I return another promise. So, theoretically, if I had three async functions to chain, I would need to put my if statement inside a promise?
Promises really help with code composition of making async calls. In other words, they allow you to compose your code in a similar manner to how you would compose a synchronous set of calls (with the use of chained .thens) and as if it the sync code was in a try/catch block (with .catch).
So, imagine that your HTTP calls were blocking - the logic you have would look like so:
var allowedAccess, data;
try {
allowedAccess = $http.get(something);
if (allowedAccess){
try{
var result = $http.get(somethingElse);
data = {data: result};
} catch (){
data = {data: "n0pe"};
}
} else {
data = {data: "n0pe"};
}
} catch (){
data = {data: "n0pe"};
}
return data;
You could simplify it a bit:
var allowedAccess, result;
try {
allowedAccess = $http.get(something);
var result;
if (allowedAccess) {
result = $http.get(somethingElse);
} else {
throw;
}
data = {data: result};
} catch () {
data = {data: "n0pe"};
}
return data;
And that would translate to the async version of:
return $http
.get(something)
.then(function(allowedAccess){
if (allowedAccess){
return $http.get(somethingElse);
} else {
return $q.reject(); // this is the "throw;" from above
}
})
.then(function(result){
return {data: result};
})
.catch(function(){
return {data: "n0pe"};
})
At least, this is the reasoning you could apply when composing code with branches and async calls.
I'm not saying that the version I presented is optimal or shorter - it is, however, more DRY because of a single error handling. But just realize that when you do .then(success, error) it is equivalent to try/catch over the previous async operation - this may or may not be needed depending on your specific circumstance.
This is how I would code this sort of problem:
// returns a promise that resolves some endpoint if allowed
function getDataWithAccess(allowed){
return allowed ? $http.get(someEndpoint) : $q.reject();
}
// do something with data
function handleData(data){
// do stuff with your data
}
// main chain
$http.get(accessCredEndpoint)
.then(getDataWithAccess)
.then(handleData)
.catch(function(err){
return { data: "n0pe" };
});
Yes, this is very much like New Dev's answer, however I wanted to make a point of extracting the functions into their own blocks. This makes the overall code much more readable.
$q will help reduce pyramid of calls like this:
async-call1.then(...
aysnc-call2.then(...
This blog post - http://chariotsolutions.com/blog/post/angularjs-corner-using-promises-q-handle-asynchronous-calls/ - offers a clean way of making multiple HTTP requests. Notice the cleaner approach using $q. In case you were hitting a single HTTP endpoint, using your method would have been just fine. I'd say, what you have is fine also; $q might allow greater flexibility in the future.
The blog post describes a service while using $q and the code looks cleaner.
service('asyncService', function($http, $q) {
return {
loadDataFromUrls: function(urls) {
var deferred = $q.defer();
var urlCalls = [];
angular.forEach(urls, function(url) {
urlCalls.push($http.get(url.url));
});
// they may, in fact, all be done, but this
// executes the callbacks in then, once they are
// completely finished.
$q.all(urlCalls)
.then(...
I am a beginner with promises also, so take this with a grain of salt.
I'm trying to rewrite the code for http://m.amsterdamfoodie.nl in a more modern style. Basically single page Angular app downloads a set of restaurants with locations and places them on a map. If the user is the Amsterdam area then the user's location is added too, as are the distances to places.
At present I manage the asynchronous returns using a lot of if (relevant object from other async call exists) then do next step. I'd like to make more use of promises would be better.
So, flow control should be:
Start ajax data download, and geolocation call
if geolocation returns first, store coords for later
once ajax data is downloaded
if geolocation available
calculate distances to each restaurant, and pass control to rendering code
else pass control immediately to render code
if geolocation resolves later, calculate distances and re-render
The patterns I find on the internet assume that all async calls must return successfully before continuing, whereas my geolocation call can fail (or return a location far from amsterdam) and that's OK. Is there a trick I could use in this scenario or are the conditional statements really the way to go?
Every time you use .then, you essentially create a new promise based on the previous promise and its state. You can use that to your advantage (and you should).
You can do something along the lines of:
function getGeolocation() {
return $http.get('/someurl').then(
function resolveHandler(response) {
// $http.X resolves with a HTTP response object.
// The JSON data is on its `data` attribute
var data = response.data;
// Check if the data is valid (with some other function).
// By this, I mean e.g. checking if it is "far from amsterdam",
// as you have described that as a possible error case
if(isValid(data)) {
return data;
}
else {
return null;
}
},
function rejectHandler() {
// Handle the retrieval failure by explicitly returning a value
// from the rejection handler. Null is arbitrarily chosen here because it
// is a falsy value. See the last code snippet for the use of this
return null;
}
);
}
function getData() {
return $http.get('/dataurl').then(...);
}
and then use $q.all on both promises, which in turn creates a new promise that resolves as soon as all the given promises have resolved.
Note: In Kris Kowal's Q, which Angular's $q service is based on, you could use the allSettled method, which does almost the same as all, but resolves when all promises are settled (fulfilled or rejected), and not only if all promises are fulfilled. Angular's $q does not provide this method, so you can instead work your way around this by explicitly making the failed http request resolve anyways.
So, then you can do something like:
$q.all([getData(), getGeolocation()])
.then(function(data, geolocation) {
// `data` is the value that getData() resolved with,
// `geolocation` is the value that getGeolocation() resolved with.
// Check the documentation on `$q.all` for this.
if(geolocation) {
// Yay, the geolocation data is available and valid, do something
}
// Handle the rest of the data
});
Maybe I'm missing something... but since you have no dependencies between the two async calls, I don't see why you can't just follow the logic you outlined:
var geoCoordinates = null;
var restaurants = null;
var distances = null;
getRestaurantData()
.then(function(data){
restaurants = data;
if (geoCoordinates) {
distances = calculate(restaurants, geoCoordinates);
}
// set $scope variables as needed
});
getGeoLocation()
.then(function(data){
geoCoordinates = data;
if (restaurants){
distances = calculate(restaurants, geoCoordinates)
}
// set $scope variables as needed
});
I'm facing this strange issue with the $http service: every request made to the API provided on the company that I work, wrapped in a $q.all(), return the same response.
var promises = [
$httpPromiseA(),
$httpPromiseB(),
$httpPromiseC()
];
$q.all(promises)
.then(function (response) {
$log.log(response);
});
// $log.log returns [expectedObjectFromA, expectedObjectFromA, expectedObjectFromA]
This occurs randomly: the expectedObjectFromA can be expectedObjectFromB or expectedObjectFromC. The fact is that all of the three objects return the same, or two of one and one of other, etc.
If I chain one after other, in a linear manner, they all work perfectly, but takes more time to acomplish the task, of course.
var def = $q.deferred();
var resolvedData = [];
$httpPromiseA()
.then(function (response) {
$log.log(response);
resolvedData.push(reponse);
return $httpPromiseB();
})
.then(function (response) {
$log.log(response);
resolvedData.push(reponse);
return $httpPromiseC();
})
.then(function (response) {
$log.log(response);
resolvedData.push(reponse);
return def.resolve(resolvedData);
});
return def.promise();
// $log.log returns [expectedObjectFromA],
// [expectedObjectFromB] and [expectedObjectFromC]
// as expected. ResolvedData brings [expectedObjectFromA,
// expectedObjectFromB, expectedObjectFromC].
Can you give me some directions on what may be happening here?
I'm using the core implementations of $http and $q. I've tried one $q.allSettled, applied as $decorator, based on the API resource of Kris Kowalk's Q, but didn't succeed too.
Thanks.
EDIT 1:
I cannot pass the arguments to the functions separately because it's a wrapper function thats call the $http service, here in my app. The wrapper function expects a String as the first argument, and an Object as the second. The wrapper function returns the $http calls.
EDIT 2:
This Plunker calls 2 concurrent requests, one to the Instagram API and the other to the Flickr API. The problem don't occur is this Plunker. I really don't know how to deal with this trouble. This is REALLY annoying.
In $q.all you'll want to pass in an array of promises, instead executing the function. $httpPromiseA.$promise instead of $httpPromiseA() and so on.
The problem was on the server side, which was having problems with simultaneous requests, answering all made together with the same response.
Thanks for all support and attention.