I have a service that runs an ajax query which upon success forwards that to a second function to parse the response into an object. The function that is initially called returns a promise using the $q library before the promise is resolved, which happens in the second function that parses the response into an object, and passes that object as a parameter to the resolve method. My controller which activates the service uses the .then method to log out the response for testing. This all works great the first time, but consecutive times it returns the resolve from the intial call, before the resolve is called a second time. How can I prevent this from happening?
Here is my code
app.controller("login", ['$scope','XMLMC', function ($scope,api) {
$scope.login = function() {
//This is bound to an ng-click directive in the current route template
var params = {
selfServiceInstance: "selfservice",
customerId: $scope.username,
password: $scope.password
};
var authenticated = api.request("session","selfServiceLogon",params).then(function(response) {
console.log(response);
//log the response once the promise is resolved or rejected
});
};
}]);
app.factory("XMLMC", ['$http', '$q', function ($http, $q) {
function XMLMC($http, $q) {
$http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
var def = $q.defer();
var P = def.promise;
var that= this;
this.prepareForPost = function(pkg) {
return JSON.stringify(pkg);
};
this.request = function(service, request, params, host, newsession) {
if(request === "analystLogon") {
newsession = true;
}
var call = {
service: service,
method: request,
params: params
};
if(host) {
call.host = host;
} else {
call.host = "localhost";
}
if(newsession) {
call.newsession = "true";
}
var pkg = {
contents: this.prepareForPost(call)
};
$http.post('php/XMLMC/api.php', jQuery.param(pkg)).success(function (response,status) {
that.consume(response, def);
//consume the response, pass the deferred object to resolve later
}).error(function (response,status) {
def.reject(response,status);
});
return P; //return the promise, not the resolved object
};
this.consume = function(response, defer) {
console.log(response);
//log the response that was received. For some reason this log happens after the log in the controller on subsequent calls to this service, not the first call.
var resp = response[0],
digested = {},
i;
digested.status = resp["attrs"]["STATUS"];
var params = resp["children"][0]["children"];
for(i=0; i < params.length; i++) {
var key = params[i]["name"];
var val = params[i]["tagData"];
digested[key] = val;
}
defer.resolve(digested);
//resolve at this point, after the response has been consumed and parsed.
};
}
return new XMLMC($http, $q);
//return new instance of this object for ease of use in controller
}]);
try this schema, i removed the creation of the deferred object and returned the promise chain initited by $http.post, not sure if will work but it feels like it should since $http returns a promise.
app.factory("XMLMC", ['$http', '$q', function ($http, $q) {
function XMLMC($http, $q) {
$http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
this.prepareForPost = function(pkg) {
return JSON.stringify(pkg);
};
this.request = function(service, request, params, host, newsession) {
if(request === "analystLogon") {
newsession = true;
}
var call = {
service: service,
method: request,
params: params
};
if(host) {
call.host = host;
} else {
call.host = "localhost";
}
if(newsession) {
call.newsession = "true";
}
var pkg = {
contents: this.prepareForPost(call)
};
return $http.post('php/XMLMC/api.php', jQuery.param(pkg))
.then(that.consume)
};
consume = function(response) {
console.log(response);
var resp = response[0],
digested = {},
i;
digested.status = resp["attrs"]["STATUS"];
var params = resp["children"][0]["children"];
for(i=0; i < params.length; i++) {
var key = params[i]["name"];
var val = params[i]["tagData"];
digested[key] = val;
}
return $q.resolve(digested);
//resolve at this point, after the response has been consumed and parsed.
};
}
return new XMLMC($http, $q);
//return new instance of this object for ease of use in controller
}]);
Related
I have a function to fetch data from a web api and load it in the UI.
I am using angular js with xmlhttprequest to fetch data.
My function is like below.
var testMethod = (resolve,reject)=>{
$scope.responseData = [];
var startCount =0;
var isDataAvaialble = true;
do {
var url = "ur/api/search?start=" +startCount;
$http({
method : 'GET',
url:url,
}).then function successCallback(response){
$scope.data= response.Data;
for (var i =0 ; i<$scope.data.length; i++)
{
// do something
if(condition)
{
$scope.resonseData.push($scope.data[i]);
isDataAvailable = true;
}
else
{
isDataAvailable =false;
break;
}
}
});
startCount ++;
}while(isDataAvailable)
resolve($scope.response);
};
But since I am using Promise inside the loop the loop is getting executed before the promise.
Instead if I use synchronous xmlhttprequest loop is working.
ie instead of
var url = "ur/api/search?start=" +startCount;
$http({
method : 'GET',
url:url,
}).then function successCallback(response){
if I use
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.send(null);
$scope.data = JSON.parse(xhr.responseText);
everything works. But I don't need to use synchronous here.
I need to get this done asynchronously. I need to get the $scope.responseData in other function
Try something like this:
$scope.responseData = [];
function testMethod(startCount = 0) {
const url = `url/api/search?start=${startCount}`;
return $http({ method : 'GET', url })
.then((response) => {
const data = response.Data;
for (let i = 0; i < data.length; i++) {
if(!condition) { return; }
$scope.responseData.push(data[i]);
}
return testMethod(startCount + 1);
});
}
testMethod();
I've got a employeeController and a employeeFactory in the employeeFactory I receive an employee like this:
function employeeFactory(authenticationFactory,requestFactory,GLOBALS) {
var factory = {};
var vm = this;
vm.employee = {};
factory.getEmployee = function(id) {
data = {"api_token": authenticationFactory.getToken()};
url = GLOBALS.url + 'show/employee/' + id;
requestFactory.post(url, data)
.then(function (response) {
return vm.employee = response.data.result.Employee;
}, function () {
$window.location.assign('/');
});
}
return factory;
}
In my controller I'm trying to receive it like this:
console.log(employeeFactory.getEmployee($routeParams.id));
But the result is null?
When I console.log the response in my requestFactory I receive an employee object. What am I doing wrong?
Reason behind it is, you missed to return promise of requestFactory.post from factory.getEmployee method
Code
factory.getEmployee = function(id) {
data = {"api_token": authenticationFactory.getToken()};
url = GLOBALS.url + 'show/employee/' + id;
return requestFactory.post(url, data)
.then(function (response) {
return vm.employee = response.data.result.Employee;
}, function () {
$window.location.assign('/');
});
}
But even though you do it, you will not able to get value employee object printed. It will print promise object return by $http.post method/ $resource method
For getting hold on that object you need to use .then function over that promise object. like below
employeeFactory.getEmployee($routeParams.id).then(function(employee){
console.log('Employee', employee)
})
We can use deferred api in angularjs for better communication between caller and service.
factory.getEmployee = function(id) {
var deferred = $q.defer();
data = {"api_token": authenticationFactory.getToken()};
url = GLOBALS.url + 'show/employee/' + id;
return requestFactory.post(url, data)
.then(function (response) {
deferred.resolve(response.data.result.Employee);
}, function () {
deferred.reject();
});
return deferred.promise;
}
On your controller:
employeeFactory.getEmployee($routeParams.id).then(function(employee){
console.log('Employee', employee)
},function(){
$window.location.assign('/');
})
By this approach we can notify caller with some messages if your response gets delayed.
For more info see this link.
angular promises
we have use $cacheFactory for get some configuration and User data one time
like
var cache = $cacheFactory("Temp");
var getCachedData = function (url) {
var data = cache.get(url);
var deferred = $q.defer();
if (data) {
deferred.resolve(data);
} else {
readFromServer(url).then(function(result) {
cache.put(url, result);
deferred.resolve(result);
});
}
return deferred.promise;
};
return {
GetCachedData: getCachedData
};
If services get data as null then it give call to server ow it will return data from Cache but if second call come be four fist complete then he give server call so how can we use lock like C# in JavaScript .
To avoid to make multiple call to the same API, you need to make something like that (I didn't test but this should work) :
var isRetrievingData = false;
var waitingDeferred = [];
var getData = function (url) {
var data = cache.get(url);
var deferred = $q.defer();
if (data) {
deferred.resolve(data);
} else if (isRetrievingData) {
waitingDeferred.push(deferred);
} else {
isRetrievingData = true;
waitingDeferred.push(deferred);
readFromServer(url).then(function(result) {
cache.put(url, result);
for(var i = 0; i < waitingDeferred.length; i++) {
waitingDeferred[i].resolve(result);
}
});
}
return deferred.promise;
};
Here how it works :
If the data is already cached, just return it by resolving the deferred
If the data is not cached and isRetrievingData is false, launch the request and set isRetrievingData to true
If the data is not cached and isRetrievingData is true, that means that another call is in progress so don't to anything, just add the deferred in an array
When the only call is finished, to send data to each caller, you have just to resolve each deferred registered in the waitingDeferred array.
Is that good for you ?
I am sort of new to Angularjs and I am trying to find the optimal structure to do such flow:
Get an access_token from my server asynchronously
Store it in a provider as a variable to be used in the future
Make async calls to the third party server with the access_token
My factory currently looks like this
app.factory('SomeFactory',['$resource', function($resource){
//access_token only needs to be set once
var access_token = 0;
var request = $resource('/my/server/').get();
request.promise.then(function(result){
access_token = result;
}
);
return $resource('https://third.party/:token',{token: access_token});
}])
I can't set the access_token, the call back function is to be destroyed somehow.
Also how can I form a chain, so that third party cannot be called until the access_token has been set first?
try this
app.factory('SomeFactory',['$resource, $q', function($resource, $q){
var service = {};
//access_token only needs to be set once
var access_token = null;
getAccessToken = function() {
var deferred = $q.defer();
if (access_token) {
deferred.resolve(access_token);
} else {
$resource('/my/server/').then(function(result){
access_token = result;
deferred.resolve(access_token);
});
}
return deferred.promise;
}
service.callThirdParty = function() {
var deferred = $q.defer();
getAccessToken.then(function(access_token) {
$resource('https://third.party/:token',{token: access_token}).then(function(result) {
deferred.resolve(result);
})
})
return deferred.promise;
}
return service;
}]);
I have been at this for over 10 hours at this point. It does not make sense to me. I desperately need clarification on what I'm doing wrong. Below is a simple factory function that makes an AJAX call for a JSON file. There are no async. data issues and everything just works. The issue I'm having is that I'm trying to save the returned result and access it later. If the variable is populated, I don't then have to make a second AJAX call, I can simple grab the contents of a local variable. I realize there are other ways of doing this, but I'm particular to using this factory method.
storyDataAsFactory.$inject = ['$log', '$http', '$q'];
angular.module('ccsApp').factory('storyDataAsFactory', storyDataAsFactory);
function storyDataAsFactory($log, $http, $q) {
var storiesCache = [];
function getStories(url) {
url = url || '';
if (url !== '') {
var deferred = $q.defer();
alert('inside getStories, length of storiesCache = ' + storiesCache.length); // this is always zero! Why?
//determine if ajax call has already occurred;
//if so, data exists in cache as local var
if (storiesCache.length !== 0) {
$log.info('Returning stories from cache.');
deferred.resolve(storiesCache);
return deferred.promise;
}
$http({method:'GET', url:url})
.success(function (data, status, headers, config) {
deferred.resolve(data);
})
.error(function (data, status, headers, config) {
deferred.reject(status);
});
return deferred.promise;
} else {
$log.error('(within storyDataAsFactory) Failed to retrieve stories: URL was undefined.');
}
}
return {
stories: storiesCache,
getStories: function(url) {
alert('inside return factory object, length of stories = ' + this.stories.length);
//getStories returns a promise so that routeProvider
//will instantiate the controller when resolved
return getStories(url);
}
};
}
You need to use a promise. I have not ran the code below, but this is the idea:
angular.module('ccsApp').service('storyDataAsFactory', storyDataAsService);
function storyDataAsService($log, $http, $q) {
var storiesCache = [];
function getStories(url) {
url = url || '';
if (url !== '') {
alert('inside getStories, length of storiesCache = ' + storiesCache.length); // this is always zero! Why?
if (storiesCache.length == 0) {
return $http({method:'GET', url:url});
} else {
var deferred = $q.defer();
deferred.resolve(storiesCache);
return deferred.promise;
}
} else {
$log.error('(within storyDataAsFactory) Failed to retrieve stories: URL was undefined.');
}
}
return {
getStories: function(url) {
return getStories(url).then(function(stories) {
// Do whatever
return stories;
});
}
};
}
storyDataAsService.getStories().then(function(stories) {
$scope.stories = stories
})