Promise returning before $http promise concludes - javascript

I need to have one function call a second function where the second function includes some setup code, an http callback to a REST server and then finely some cleanup code before returning to the first function. The idea is to have the first function then displays a result message after the second function is finished.
The following example returns from the second function before it finishes the callback so I don't get the results of the http success or error.
var saveData = function(_this){
return new Promise(function(resolve,reject){
resolve( _this.Save('SavExit') );
});
};
saveData(this).then(function(httpResponse){
// display response after http callback finishes
console.log(httpResponse);
});
this.Save = function (lcAction) {
// validate data
$http.post('serverCallback.aspx',oSelectedVendor).
success(function (data, status, headers, config) {
// process data before returning;
return true;
}).
error(function(data,status,headers,config){
console.log(data);
return false;
});
};

You need to return Promise from the Save method:
this.Save = function (lcAction) {
// validate data
return $http.post('serverCallback.aspx',oSelectedVendor).
success(function (data, status, headers, config) {
// process data before returning;
return true;
}).
error(function(data,status,headers,config){
console.log(data);
return false;
});
};
Note return I added in front of $http.
In addition, since you return Promise from Save, you don't need another one in saveData:
var saveData = function(_this) {
return _this.Save('SavExit')
};

Related

How to get response from service on controller

I'm trying to separate the $http.post() call into a ".factory()", But would like to fetch the response which is coming async on the controller. Is there a way of doing that?
Controller:
Login.post($scope.user);
Factory:
.factory( 'Login' , function($http,SERVERURL){
var serverUrl = SERVERURL;
return {
'post' : function(user){
$http.post(serverUrl+'/login', user).
then(function(response) {
console.log(response);
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
}
};
})
There is a .then() but I want that on the controller, so I can behave accordingly. Thank you!
Basically you need to return the $http.post promise, and from success function you could return a data that will return to the consumer of this method. So that you could easily call the factory method from controller & inside .then function of that call you could have success and error function.
Code
.factory('Login', function($http, SERVERURL) {
var serverUrl = SERVERURL;
return {
'post': function(user) {
return $http.post(serverUrl + '/login', user).
then(function(response) {
console.log(response);
return response.data; //return data from here
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
}
};
})
Controller
Login.post().then(function(data){ //success function
console.log(data)
}, function(error){ //error function
console.log(error);
})
You could add a callback param.
.factory( 'Login' , function($http,SERVERURL){
var serverUrl = SERVERURL;
return {
'post' : function(user, callback){
$http.post(serverUrl+'/login', user).
then(function(response) {
console.log(response);
callback(null, response);
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
callback(response);
});
}
};
})
And your controller will become:
Login.post($scope.user, function(err, response) {
if(err) {} //do something if there is an error
// or deal with the response
});
To return any response to controller just do:
return {
'post' : function(user){
return $http.post(serverUrl+'/login', user);
}
};
In your controller you will already call.then()
Angular's $http methods return a Promise.
The $http API is based on the deferred/promise APIs exposed by the $q service.
Factory
Your method post is not yet returning anything but can quite simply return the Promise which is created by calling $http.post:
.factory('Login' , function($http, SERVERURL){
var serverUrl = SERVERURL;
return {
'post' : function (user) {
return $http.post(serverUrl + '/login', user)
// ^^^^^^
.then(function (response) {
console.log(response);
return response.data;
}, function (response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
}
};
});
Controller
Then consume the result of the returned Promise by calling then on it:
Login.post($scope.user).then(function (res) {
// do something with `res`...
});

AngularJS: Why is .then() not waiting for the promise object to arrive

I have created the following angular service for the purpose of making the API calls.
(function (module) {
module.service('APIService',function ($http, $q) {
console.log("Reached APIService")
var deferred = $q.defer();
this.getData = function (requestURL) {
console.log("Reached APIService #GET", requestURL);
$http.get(requestURL).success(
function (data, status) {
deferred.resolve(data);
console.log("In service",status,data);
}).error(
function (data, status) {
deferred.reject(data);
console.log(status);
});
return deferred.promise;
};
this.postData = function (requestURL, requestObj) {
console.log("Reached APIService #POST", requestURL, requestObj);
$http.post(requestURL, requestObj).success(
function (data, status) {
deferred.resolve(data);
console.log(status);
}).error(
function (data, status) {
deferred.reject(data);
console.log(status);
});
return deferred.promise;
};
});
}(angular.module("MainApp")));
I have injected it in my two controllers. However, I am facing following issues:
When I call it first time in first controller, it works fine and returns the desired result. However, when I call it in second controller as follows:
APIService.getData(Config + headerURL).then(function (response) {
console.log(Config + headerURL);
console.log("header response from API", response);
},function(error) {
console.log("API call for header data failed");
});
Since my service returns a promise object, I don't expect the code inside .then() to work before the service data arrives.
However, it runs before service data arrives (no clue how). The most strange thing is that the response that I get inside .then() is not actually the response from this URL (Config+headerURL), but it is the response that was obtained from a different URL previously called in first Controller using the same APIService service.
Just to inform: the actual response from current URL do arrive at a later stage.
I am aware of asynchronous callings and I think it has something to do in this case, but I guess .then() handles it in Angular. So I am confused what is the issue here. Can anyone shed some light please?
Since the service is a singleton, you only have a single instance of the deferred object.
Once resolved, it will keep being resolved, so the next time you call, getData, it will return immediately.
You could move:
var deferred = $q.defer();
inside both your getData and postData function.
Or you could just return the promise that $http creates.
Just try this, You need to return to the http result.
this.getData = function (requestURL) {
console.log("Reached APIService #GET", requestURL);
return $http.get(requestURL) };
Looks like you have to disable cache explicitly within $http call.
$http.get({
url: requestURL,
cache: false
})
You are using an anti-pattern by creating your own promises when $http already returns a promise.
Get rid of $q altogether in this service and simply return $http
this.getData = function (requestURL) {
console.log("Reached APIService #GET", requestURL);
return $http.get(requestURL).then(function (response) {
return response.data;
console.log("In service", status, data);
}, function () {
// errror handling here
});
};

angular, correct way for a function callback

I am writing an ItemProvider for my app in angular js.
I chose a service.
app.factory('ItemProvider', function($http) {
var url = "http://localhost:7888/api.php/json?";
return {
get_data: function() {
$http.get(url).
success(function(data,status,headers,config) {
json = data;
console.log("app returned ok");
console.log(json);
callback(json);
}).
error(function(data,status,headers,config) {
console.log("Error getting data from app!");
json = data;
callback(json);
});
callback = function(json) {
console.log("callback");
return json;
}
console.log("already done");
}
};
});
Of course what happens here is that get_data returns immediately before the actual calls to the backend via $http returned...
How do I correctly have a get_data function which will return the data from the backend? I tried adding a callback (see code above) but I realize by the time it's getting called, get_data already finished as well...
$http is hardcoded to only work asynchronously, meaning your only option is to code with that in mind. Due to this, it isn't possible for get_data to directly return the data, instead, it has to either accept a callback, or return a promise. The promise route is far easier in my opinion.
app.factory('ItemProvider', function($http) {
var url = "http://localhost:7888/api.php/json?";
return {
get_data: function(url) {
return $http.get(url);
}
};
});
example usage:
//...
ItemProvider.get_data('/items')
.success(function (items) {
console.log(items);
})
.error(function () {...});
//...

Using the $q implementation in Angular how can I loop a promise until success?

As per my other recent questions, I'm trying to persist data to a server for an angular application that's targeted at mobile devices (unstable connections), so it should keep trying the request until success.
How can I do this with promises?
At the moment I've got:
Service:
this.addObject = function addObject(object) {
var deferred = $q.defer();
var httpConfig = {
method: 'POST',
url: 'http://api.foo.com/bar',
data: object
}
setTimeout(function() {
$http(httpConfig).
success(function(data, status) {
deferred.resolve('Woohoo!');
}).
error(function(data, status) {
deferred.reject('Couldnt reach server this time...');
});
}, 3000);
return deferred.promise;
}
Controller:
myService.addObject(myObject)
.then(function(message) {
console.log(message);
}, function(message) {
console.log(message);
});
I can't remove the reject callback, as the code won't seem to execute without it, but once the reject is called, it breaks the setTimeout loop. How can I force the promise to repeat until the success callback?
This is the proper form of the answer at AngularJS service retry when promise is rejected
this.addObject = function addObject(object) {
var counter = 0;
var deferred = $q.defer();
var httpConfig = {
method: 'POST',
url: 'http://api.foo.com/bar',
data: object
}
var doRequest = function() {
counter++;
var self = this,args = arguments;
$http(httpConfig).
success(function(data, status) {
deferred.resolve('Woohoo!');
}).
error(function(data, status) {
//just fail already, it's not working
if(counter > 5) {
return deferred.reject('Couldnt reach server this time...');
}
//this will re-call doRequest();
args.callee.apply(self);
});
}
doRequest();
return deferred.promise;
}
As you're doing with with $http, http interceptors can do this. If you want a http request to infinitely loop until it returns a success:
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('RetryInterceptor');
}]);
app.factory('RetryInterceptor', function($timeout, $injector) {
return {
'responseError': function(rejection) {
// Manual inject to work around circular dependency issue
var $http = $injector.get('$http');
return $timeout(function() {
return $http(rejection.config);
},3000);
}
}
});
This works due to how the initial call to $http won't resolve until (in this case) all the responseError interceptors have been resolved.
You can see this in action at http://plnkr.co/edit/QAa9oIK4lTM6YwccEed3?p=preview (looking in the console log there is a failed request every 3 seconds.)
Note: there might need to be more logic to make sure it only retries on the right sort of error. i.e. it's not a real 404 where in fact the browser is to blame, for example.
it should keep trying the request until success. How can I do this with promises?
By "recursively" calling the function again in the error handler so that you're resolving the promise with the result of the next try.
this.addObject = function addObject(object) {
var httpConfig = {
method: 'POST',
url: 'http://api.foo.com/bar',
data: object
}
return $http(httpConfig).then(function(data, status) {
return 'Woohoo!';
}, function(data, status) {
throw 'Couldnt reach server this time...';
});
}
this.addObjectForever = function addObject(object) {
var that = this;
return this.addObject(object).then(null, function(err) {
console.log("Failed this time");
return $timeout(3000).then(function() {
console.log("Trying again now");
return that.addObjectForever(object);
});
});
};
instance.addObjectForever(obj).done(function() {
console.log("It eventually worked");
});
quite crowded here :)
my solution:
angular.module('app', [])
.service('Service',function($q,$http) {
this.addObject = function(object) {
var httpConfig = {
method: 'POST',
url: 'http://api.foo.com/bar',
data: object
}
var deferred = $q.defer();
$http(httpConfig)
.success(function(data, status) {
deferred.resolve('Woohoo!');
})
.error(function(data, status) {
deferred.reject('Couldnt reach server this time...');
});
return deferred.promise;
};
})
.controller('MainCtrl',function($scope,$interval,Service){
/*Service.addObject({})
.then(function(message) {
console.log(message);
}, function(message) {
console.log(message);
});*/
$interval(function(){
Service.addObject().then(function(message) {
console.log(message);
}, function(message) {
console.log(message);
});
},1000);
})
Uhm. Even though there are HTTP interceptors for this particular case, one of the advantages of working with promises instead of callback is that you can use higher order functions.
// both p and q are functions that create a promise
p = makeSomePromise(options)
q = repeat(10, p)
That is, for instance, a function that takes a promise making function and produces a new promise making function that retries the other promise repeatedly, until a max.
For example: (This is for nodeJS using kew, but you should get the point)
var Q = require('kew');
function retryPromise(n, mkPromise) {
if(n > 0)
return mkPromise()
.fail(function(){
console.log('failed promise, retrying maximum ' + n + ' more times');
return retryPromise(n - 1, mkPromise);
});
return mkPromise();
}
(function(){
var mkOp = function(){
if(Math.random() > .1)
return Q
.fcall(function(){return 1;})
.then(function(){throw Error('aah');});
return Q.fcall(function(){return 'yay'});
};
retryPromise(10, mkOp)
.then(function(data){
console.log(data);
})
.fail(function(err){
console.log('failed', err);
});
}());

$http error callback does not work in combination with httpProvider responseInterceptor

I added in my application a "loading screen". As found in this post: 'Click'
Now I have the problem that all $http request results in the "success" callback. Even when the url does not exist.
$http.post("this doesnt even exist", { })
.success(function (data, status, headers, config) {
alert("success"); //this callback is called
})
.error(function (data, status, headers, config) {
alert("error"); //never called
});
When I disable the 'responseInterceptor' everything works fine. (exception, not found, wrong parameters -> all results in error callback)
I'm using a .NET Webservice to get my data from.
The values of parameters in success callback
data: ''
status: 0
headers: 'function(name) { ... }'
config: JSON.stringify(config) '{"method":"POST","url":"this doesnt even exist","data":{}}'
It's because the response interceptor you linked to "swallows" the error.
In the following code:
return promise.then(hide, hide);
The first hide is for the success callback, while the second is for the error callback.
The hide function itself just ends with return r; in all cases, which means it will return the response.
For your $http.post to know there was an error the response interceptor needs to return the promise as rejected instead: return $q.reject(reason);
Something along these lines should hopefully work or at least give further guidance (note that I have not been able to test it):
$httpProvider.responseInterceptors.push(function ($q) {
return function (promise) {
numLoadings++;
loadingScreen.show();
var hide = function () {
if (!(--numLoadings)) loadingScreen.hide();
};
var success = function (response) {
hide();
return response;
};
var error = function (reason) {
hide();
return $q.reject(reason);
};
return promise.then(success, error);
};
});

Categories

Resources