Promise always returns the initial value - javascript

having a bit of a problem with promises in angularjs.
My promises 'get cached', meaning they always return the initial value they got called with. I'm pretty familiar with promises from other places, but new to angularJS, so please help me shed a light on my problem, I'm probably not understanding something very basic here
I am using a factory:
.factory('Traffic', function ($http) {
var trafficUrl = 'some url';
var httpPromise = $http.get(trafficUrl, {cache: false} );
var invalidateCache = function() {
return $http.get(trafficUrl, {cache: false} );
}
return {
all: function () {
httpPromise = invalidateCache();
return httpPromise
.then(function (response) {
//parsing the response and returning stuff (not promise)
}
}
})
which is sending a request, and parsing it for the first time.
now invalidateCache was suggested by someone to avoid exactly my problem (assign a new $http.get each time to avoid it referring to the same, initial promise).
now my controller:
.controller('TrafficCtrl', function ($interval, $ionicLoading, $scope, Traffic) {
var getJams = function () {
var traffic = Traffic.all();
traffic.then(function (response) {
//doing stuff with the response
})
};
$scope.refresh = function () {
getJams();
}
})
now, each time I invoke $scope.refresh method, I see the items getting 'refreshed' (console.log gets called inside getJams) but the values stay as the first called getJams().
Thanks.

From your comment, it sounds like the browser is caching your response, so you will want to update the server logic to set Cache specific headers.
You will probably want to add the following Cache-Control header to your response:
Cache-Control: no-store, no-cache
A little more info about the cache headers here.
Should be able to find plenty of examples to set this in your server side language of choice.
Also, you can clean up the code much more:
.factory('Traffic', function ($http) {
var trafficUrl = 'some url';
return {
all: function () {
// don't need the invalidate call anymore
return $http.get(trafficUrl).then(function (response) {
//parsing the response and returning stuff (not promise)
}
}
})
And your controller:
var getJams = function () {
// No need to store the initial promise call, just chain them.
Traffic.all().then(function (response) {
//doing stuff with the response
})
};

Related

How angular interceptors works?

I was learning angular interceptors today. I did some samples to to better understand the concept. Here is small sample.
var app = angular.module("myApp", []);
app.factory("timestampMaker", [
function() {
var timestampMaker = {
request: function(config) {
console.log(config);
config.requestTimestamp = new Date().getTime();
return config;
},
response: function(response) {
response.config.responseTimestamp = new Date().getTime();
return response;
}
};
return timestampMaker;
}
]);
app.config(['$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('timestampMaker');
}
]);
app.run(function($http) {
$http.get('https://api.github.com/users/naorye/repos').then(function(response) {
console.log(response);
var time = response.config.responseTimestamp - response.config.requestTimestamp;
console.log("The request took" + (time / 1000) + "seconds")
});
});
<html ng-app="myApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</head>
</html>
When I am doing console.log(config) inside request function, here is the output on the console.
I am not getting how responseTimestamp appear in the config object of request where as its defined inside response function
Note I used the source code for 1.2.x which is the one added in the question.
$httpProvider is a provider that gives you $http service.
As a provider, in config time exposes a public array - there you just add all strings from services names you wanted to be injected in your response/requests.
That is what you do here
app.config(['$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('timestampMaker');
}
]);
and it can be only done in config time because per docs providers can be configured before the application starts
You can see the exposed array in the source code in line 159
var responseInterceptorFactories = this.responseInterceptors = [];
When you request the $http service, injecting it into your service/controller,
$get function is executed. In that function, your array of interceptors is iterated, as you can see in source code in line 179
var reversedInterceptors = [];
forEach(responseInterceptorFactories, function(interceptorFactory, index) {
var responseFn = isString(interceptorFactory) ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory);
/**
* Response interceptors go before "around" interceptors (no real reason, just
* had to pick one.) But they are already reversed, so we can't use unshift, hence
* the splice.
*/
reversedInterceptors.splice(index, 0, {
response: function(response) {
return responseFn($q.when(response));
},
responseError: function(response) {
return responseFn($q.reject(response));
}
});
});
Per convention, they reverse the order. You can see that using the string, they get a reference to the function using $injector.get or $injector.invoke, and using $q.when() we can introduce them to the promises chain, even if they are synchronous code - See this Can I use $q.all in AngularJS with a function that does not return a .promise? if you are not sure what I meant about $q.when()
So far we have an array with functions, which are all promise-like (thanks to $q.when()). When you request data through $http like this
$http.get('https://api.github.com/users/naorye/repos').then(function(response) {
console.log(response);
var time = response.config.responseTimestamp - response.config.requestTimestamp;
console.log("The request took" + (time / 1000) + "seconds")
});
even though you have the .get(), is just a shortcut for all the same functionality which is here
In the code the relevant part is this one:
First, a chain array is created with two values: an inner function which is not important for our purpose (but it returns a promise - this is important), and undefined value.
Our array with interceptors is iterated, and request interceptors are added at the beginning (before the request) and response at the end. Like this
function serverRequest {
// some code
};
var chain = [serverRequest, undefined];
var promise = $q.when(config);
// apply interceptors
forEach(reversedInterceptors, function(interceptor) {
if (interceptor.request || interceptor.requestError) {
chain.unshift(interceptor.request, interceptor.requestError);
}
if (interceptor.response || interceptor.responseError) {
chain.push(interceptor.response, interceptor.responseError);
}
});
then, having the chain complete (remember our array of interceptors was full of promises), the code iterates it, adding all of them using .then(), causing all of them to be executed in a chain, following promises chaining (you can google that)
while(chain.length) {
var thenFn = chain.shift();
var rejectFn = chain.shift();
promise = promise.then(thenFn, rejectFn);
}
Finally, the callback you add in success and error is added at the very end of the chain and the promise is returned
promise.success = function(fn) {
promise.then(function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
};
return promise;
The only part I am not sure about is the use of undefined in the chain array
Summary
You add the name for your services, $HttpProvider uses $invoke service to get them and adds them in the promise chain using $q.when(), returning a promise. At the end of that, your callbacks for a specific $http request is added.

Angular Promise not working

I try to get some important things like: companyid,employeeid etc. with every request that a user makes. So this has to be received before everything else is done.
After that the user receives information based on his companyid that he sets with every request (get/company/{companyid}).
The problem that I have is that the response for receiving the companyid takes to long and angular already tries to make a request to (get/company/{companyid}) obviously there is no companyid yet.
I've tried to fix this whit promise but it's not working.
Here I try to receive some important information about the user(that I do with every request) :
Service
(function () {
angular.module('employeeApp')
.service('authenticationservice', authenticationservice);
function authenticationservice($http,$location,authenticationFactory,$q,GLOBALS,$cookies) {
this.validateUser = function () {
var vm = this;
vm.deferred = $q.defer();
data = {"api_token": api_token};
return $http.post(GLOBALS.url+'show/employee/' + $cookies.get('employeeid'),data)
.success(function(response)
{
vm.deferred.resolve(response);
})
.error(function(err,response)
{
vm.deferred.reject(err);
});
return vm.deferred.promise;
}
}
})();
Routes file
(In my routes file I use the authenticationservice to set all important users variables.)
employeeAppModule.run([
'authenticationservice',
'constants',
function(authenticationservice,constants) {
authenticationservice.validateUser()
.then(function(response)
{
constants.companyid = response.result.Employee;
constants.role = response.result.Role;
constants.name = response.result.FirstName;
console.log('test');
},
function(response){
console.log('error');
});
}
]);
So the problem is that the user information is set to late and angular already goes to my homeController where he uses the companyId that is not being set yet.
Thankyou
The problem in your current code is return $http.post are having two return statement in your validateUser method. Which is returning $http.get before returning return vm.deferred.promise; & that why customly created promise doesn't get returned from your method. Though by removing first return from $http.get will fix your problem, I'd not suggest to go for such fix, because it is considered as bad pattern to implement.
Rather I'd say, you should utilize promise return by $http method, & use .then to return data to chain promise mechanism.
Code
function authenticationservice($http, $location, authenticationFactory, $q, GLOBALS, $cookies) {
this.validateUser = function() {
var vm = this;
data = {
"api_token": api_token
};
return $http.post(GLOBALS.url + 'show/employee/' + $cookies.get('employeeid'), data)
.then(function(response) {
var data = response.data;
retrun data;
}, function(err) {
return $q.reject(err);
});
}
}
To make sure that $ http return a $ promise object you need to check that the action in the controller returns a value and it is not a void action.

AngularJS n:n promise and data synchronization / sequencing

BACKGROUND
As my app grows I’m struggling more and more with AngularJS promise synchronization / sequencing across multiple controllers and services. In my example below I have an articles controller ArticleController and related service ArticleDataService that in the initial load process
gets / loads articles from a server,
selects the first article from articles and
uses this current article currentArticle to get the related images from the server.
THE PROBLEM
Data load from the server takes approx. 1 second to return the articles records, then as above select first article and then also return the related image records from the server. The problem is that during that latency period the second controller (ImagesController) is looking for the cached data in the second Services module (ImageDataService) and cannot find it because the first promises have obviously not resolved from the Article Controller due to server latency and as such the ArticleController hasn't cached the images data yet, which then blows up any following image related code. As you can see below, if I try to return a $q.when(cachedImages) on cachedImages, it will returns a promise, but that promise is never resolved. As both controllers are using separate $q resolve sequences it makes sense, but without building an uber controller I'm unsure how to fix the sequencing issues.
EDIT
I'm trying to solve for the n:n chaining / sequencing problem
Most tutorials or discussions tend to focus on 1:1 or 1:n chaining which works perfectly. No problem there.
It is the n:n where I'm having problems i.e. ctrl to service, service to service and n ctrl to service. Most of what I can find on n:n are basic tuts loading simple static arrays or object which doesn't have the latency issue.
ATTEMPTED APPROACHES
rootscope / watch : I've tried $rootscope.$watch() events inside of services as well as watch in controllers i.e. an event based approach on the imageCached var inside of the ImageDataService, but frankly I find that messy as there can be unnecessary overhead, debugging and testing issues.That said, it does work but every now and then I will see lots of iteration when console logging a deeply nested var which makes the event approach seem black boxish.
EDIT - watch approach
Example: I can add the following to the 2nd controller ImageController or in the ImageDataService which works, as would $emit, and then kill the watcher, but as I said this does require a bit of time management for dependent methods such as chart data directives. Also, I wondering if mixing promises and events is bad practice or is that the accepted best practice in JS?
var articleModelListener = $scope.$watch(function () {
return ImageDataService.getImages();
},
function (newValue, oldValue) {
if (newValue !== undefined && newValue !== null) {
if (Object.keys(newValue).length > 0) {
iCtrl.dataUrls = newValue;
// kill $watcher
articleModelListener();
}
}
});
timeout : I've also tried to wrap all ImageController code in timeout AND also document ready, but I find that has further repercussions down the line e.g. in my Chart and Poll controllers I have to wrap directives in additional $timeouts or $intervals to adjust for the ImagesController time intervals or the directives won't load the DOM attributes correctly, so it becomes a chain of app performance death.
uber DataService service or factory data resolution : I've tried to resolve all data in an uber DataServices service provider but I find I have the same issue now in all controllers as although the uber service fixes the sequencing I now need to get synchronization with uber and all controllers. I know async ... give me state programming any day :)
QUESTION & ASSUMPTION
Assumption: timeout and interval wrapping are bad practices / anti-pattern as that is waterfall?
Is the best approach to stick with promises and if so is there a way to get promises to synchronize or said better sequentially resolve across multiple controllers / services OR do I keep going down the events approach using rotoscope watches and controller scope watches?
Example of my code / problem below:
PLEASE NOTE:
1. I've removed code for brevity sake i.e. I have not tested this summary code, but rather using it to example the problem above.
2. All and any help is much appreciated. Apologies for any terminology I've misused.
HTML
<section ng-controller="MainController as mCtrl">
// removed HTML for brevity sakes
</section>
<section ng-controller="ImagesController as iCtrl">
// removed HTML for brevity sakes
</section>
Angular JS (1.4.*)
<pre><code>
angular.module('articles', [
])
.controller('ArticlesController', ['ArticleDataServices', 'ImageDataService', function(ArticleDataServices, ImageDataService) {
var mCtrl = this;
mCtrl.articles = {};
mCtrl.currentArticle = {};
mCtrl.images = {};
var loadArticles = function () {
return ArticleDataServices
.getArticles()
.then(function (articles) {
if(articles.data) {
mCtrl.articles = articles.data;
return mCtrl.articles[Object.keys(mCtrl.articles)[0]];
}
});
},
loadCurrentArticleImages = function (currentArticle) {
return ImageDataService
.getArticleImages(currentChannel)
.then(function (imagesOfArticles) {
if(imagesOfArticles.data) {
return mCtrl.images = imagesOfArticles.data;
}
});
},
cacheImages = function (images) {
return ImageDataService
.parseImages(images)
.then(function () {
});
};
loadChannels()
.then(loadArticles)
.then(loadCurrentArticleImages)
.then(cacheImages);
}])
</code></pre>
NOTE : it is in the ImagesController below where things go wrong as this controller is executing its methods ahead of the first controller which is still waiting on data from server i.e. cachedImages or promise is not returning.
<pre><code>
.controller('ImagesController', ['ImageDataService', function(ImageDataService) {
var iCtrl = this;
mCtrl.images = {};
var getCachedImageData = function () {
return ImageDataService
.getCachedImageData()
.then(function (images) {
if(images) {
return mCtrl.images = images;
}
});
}
}])
.service('ArticleDataServices', ['$http',', $q', function($http, $q){
var model = this,
URLS = {
ARTICLES: 'http://localhost:8888/articles'
},
config = {
params: {
'callback': 'JSON_CALLBACK',
'method': 'GET',
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}
};
model.getArticles = function() {
config.params['url'] = URLS.ARTICLES;
return $http(config.params);
};
return {
getArticles: function () {
var deffered = $q.defer();
deffered.resolve(model.getArticles());
return deffered.promise;
}
}
}])
.service('ImageDataService',['$http',', $q', function($http, $q){
var model = this,
URLS = {
IMAGES: 'http://localhost:8888/images'
},
cachedImages,
config = {
params: {
'callback': 'JSON_CALLBACK',
'method': 'GET',
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}
};
model.getArticleImages = function(currentArticle) {
config.params['url'] = URLS.IMAGES + '/' . currentArticle.slug;
return $http(config.params);
};
// Return images or $q.when
model.getCachedImageData = function() {
if (cachedImages) {
return cachedImages
} else {
return $q.when(cachedImages);
}
};
model.setImageCache = function(images) {
cachedImages = images;
};
return {
getArticleImages: function (currentArticle) {
var deffered = $q.defer();
deffered.resolve(model.getArticleImages(currentArticle));
return deffered.promise;
},
setImageCache:function (images) {
return model.setImageCache(images);
},
getCachedImageData:function () {
return getCachedImageData();
}
};
}]);
</code></pre>
Your problem is common for people when initializing with angular. The correct is return promise in your service as:
app.controller("AppController", function($scope, $ajax){
$ajax.call("/people", "", "POST").then(function(req) {
$scope.people = req.data;
});
});
app.factory("$ajax", function($http) {
function ajax(url, param, method) {
var requisicao = $http({
method: method,
url: url,
data:param
});
var promise = requisicao.then(
function(resposta) {
return(resposta.data);
}
);
return promise;
}
return({
call:ajax
});
});
Note that the variable is populated only in the return of service. It is important you put all methods or anything else that makes use of such a variable within Then method. This will ensure that these other methods will only be executed after returning from the backend

How do I return Vue resource without putting to data?

I want to return result directly (Normally we try to put to data and access that data yeah. but now how I want is directly). Like when we call hello() function I want to return result variable.
I try like the following, but it doesn't return yeah. How do I try?
hello: function() {
var result = "";
this.$http.get(window.location.href).success(function(data) {
result = data;
}).error(function (data, status, request) {
});
return result;
}
Looks like it doesn't return the data because the request is async. Your method sets up the request and then returns immediately, synchronously, before the request had a chance to assign any data to the result variable.
You can try to change your code slightly:
hello: function() {
var result = {};
this.$http.get(window.location.href).success(function(data) {
result.data = data;
result.ready = true;
}).error(function (data, status, request) {
});
return result;
}
That should enable you to access the data elsewhere (as result.data), but only after the async query has succeeded. The result.ready flag would tell you if and when that has happened.
In my opinion, it would definitely be better to work with promises, though, e.g. using jQuery Deferreds and promises, or ES6 promises along with a polyfill.

angularjs - $http reading json and wait for callback

I am trying to read data from json and wait until data will be fetched into $scope.urls.content. So I write code:
$scope.urls = { content:null};
$http.get('mock/plane_urls.json').success(function(thisData) {
$scope.urls.content = thisData;
});
And now I am trying to write something like callback but that doesn't work. How can i do that? Or is there any function for this? I am running out of ideas ;/
Do you mean that ?
$http.get('mock/plane_urls.json').success(function(thisData) {
$scope.urls.content = thisData;
$scope.yourCallback();
});
$scope.yourCallback = function() {
// your code
};
You want to work with promises and $resource.
As $http itself returns a promise, all you got to do is to chain to its return. Simple as that:
var promise = $http.get('mock/plane_urls.json').then(function(thisData) {
$scope.urls.content = thisData;
return 'something';
});
// somewhere else in the code
promise.then(function(data) {
// receives the data returned from the http handler
console.log(data === "something");
});
I made a pretty simple fiddle here.
But if you need to constantly call this info, you should expose it through a service, so anyone can grab its result and process it. i.e.:
service('dataService', function($http) {
var requestPromise = $http.get('mock/plane_urls.json').then(function(d) {
return d.data;
});
this.getPlanesURL = function() {
return requestPromise;
};
});
// and anywhere in code where you need this info
dataService.getPlanesURL().then(function(planes) {
// do somehting with planes URL
$scope.urls.content = planes;
});
Just an important note. This service I mocked will cache and always return the same data. If what you need is to call this JSON many times, then you should go with $resource.

Categories

Resources