I am making a request to URLs in my forEach loop. I want the calls to be made synchronously...
angular.forEach(param, function(value) {
FactoryWithURLCallingFunction.URLCallingFunction(param1, value, param3).success(function(data){
console.log("Url returns: " + data);
});
});
Any help on this?
You should not make synchronous AJAX requests (although, it's possible), it's a sure way to making irresponsive UI. Instead, use proper promises capabilities:
$q.all(param.map(function(value) {
return FactoryWithURLCallingFunction.URLCallingFunction(param1, value, param3).then(function (response) {
console.log('Url returns:', response.data);
return response.data;
});
})).then(function(data) {
console.log('All loaded:', data);
});
Related
Is this a bad way to do this? I'm basically chaining promises, where each successful return from server, launches a new http.get() for more information BUT NOT when it errors. No more http.get()s if it causes an errorCallback!
$http.get(...).then(function() {
$http.get(...).then(function(){
$http.get(...).then(function(){}, function(){}),
function(){}); },
mainErrorCallback);
Would it make a difference if it was instead of "$http.get()" it does "ViewsService.loadViews()" inside the
$http.get(...).then( function() { ViewsService.loadViews(); }, function(){ console.log("error"); }).
EDIT: Here's what I mean, synchronously.. it seems like it works, but code needs cleanup/efficency to look a little neater:
http://jsfiddle.net/4n9fao9q/6/
(with delayed http requests): http://jsfiddle.net/4n9fao9q/26
$http.get(...).then((res) => {
//res has data from first http
return $http.get(...);
}).then((res) => {
//res has data from second http
return $http.get(...);
}).then((res) => {
//res has data from third http
}).catch((err) => {
//exploded
});
I think is cleaner. You can replace $http.get with whatever function returns a promise. If ViewsService.loadViews() returns a promise, you can use it.
As asked in the comments.
...
ViewsService.loadViews = function() {
//returns a promise
return $http.get(...);
}
OR
ViewsService.loadViews = function() {
return new Promise((resolve, reject) => {
$http.get(...).then((result) => {
//whatever
return resolve();
})
})
return $http.get(...);
}
With any of this options for loadViews you can do ViewsService.loadViers.then(etc)
Is this a bad way to do this?
Efficiency
Unless you are using the response from the first request as input to the following request then this isn't
a very efficient way to do this, as each request will be blocked until the previous one has returned. A better
way would be to use $.all($http.get(...),$http.get(...))
Style
The nested calls (the pyramid of doom) are difficult to read. As each call has the same failure response you could just chain these calls instead. e.g.
$http.get(..).then
($http.get(..)).then(
($http.get(..)).catch(errHander)
If a Javascript function is designed with callbacks, how does one encapsulate that function within an AngularJS promise?
For example, I am looking at using the following Cordova plugin: cordova.plugins.diagnostic (see https://www.npmjs.com/package/cordova.plugins.diagnostic). Many of its functions are designed with callbacks. Because the requests are engaging the device's OS, it may take a little time before a function completes, so I'm considering whether they should be called within a promise structure. For example, how would one convert the following:
cordova.plugins.diagnostic.isWifiEnabled(function(enabled){
<do something>
}, function(error){
<do something>
});
or really any generic callback structure...
masterFunction(function(enabled){
<do something>
}, function(error){
<do something>
});
to operate within an AngularJS promise? Would it be something like this?
function callMasterFunction() {
var deferred = $q.defer();
masterFunction(function(enabled){
<do something>
deferred.resolve(enabled);
}, function(error){
<do something>
deferred.resolve(error);
});
return deferred.promise;
}
I would think that this would also be a concern when using AngularJS with Cordova and the W3C Geolocation APIs. It seems to me that I may not have a clear picture of how the scope is being managed in these cases.
Ultimately, I can see chaining many of these sorts of calls together. Something like:
var promise = callMasterFunction1()
.then(function(response) { return callMasterFunction2(); })
.then(function(response) { return callMasterFunction3(); })
...
Any help would be appreciated. Thank you for your time.
You can use the promise constructor to create a promise from a callback-based API:
function callMasterFunction() {
return $q(function (resolve, reject) {
cordova.plugins.diagnostic.isWifiEnabled(resolve, reject);
});
}
Now callMasterFunction() returns a promise:
callMasterFunction()
.then(function (enabled) {
console.log('Wifi is ' + (enabled ? '' : 'not ') + 'enabled.');
})
.catch(function (error) {
console.error('Something went wrong: ', error);
});
And when you want to chain them, you can do this:
var promise = callMasterFunction1()
.then(callMasterFunction2)
.then(callMasterFunction3);
I think I may be missing something fundamental about how a promise works, because I never seem to be getting the result I expect, so hoping someone can correct my thinking.
In this case, it is a relatively easy call. I have a angular factory that among other things gets some data from the API (which makes a call to mongodb).
As there is a $http request (which is asynchronous), I wrapped the http call in a $q function which should return a resolve if successful and a reject if an error.
factory.loadLayout = function (layoutName) {
return $q(function(resolve, reject){
$http.get('/api/getlayout/'+layoutName)
.success(function (data) {
layout = data;
console.log("Got " + layout.name);
resolve('OK');
})
.error(function(data,status,headers,config){
reject(new Error( status));
});
});
}
I then have another function which is dependent on the data collected in the first function, called getButtonId, but as far as I can tell, and even though it is wrapped in the .then, it seems like it is called before the promise is resolved.
var promise = padArea.loadLayout(layoutName);
promise.then(padArea.getButtonId('A0'));
So what am I missing?
== UPDATE ==
So trying the same thing using q.defer
factory.loadLayout = function (layoutName) {
var defer = $q.defer()
$http.get('/api/getlayout/'+layoutName)
.success(function (data) {
layout = data;
console.log("Got " + layout.name);
defer.resolve('OK');
})
.error(function(data,status,headers,config){
defer.reject(new Error( status));
});
return defer.promise;
}
Still not working as I expect, and the function inside .then is still called before http have completed.
== UPDATE 2 ==
OK, so got it working (if I just call the function in the factory inside the .then, it calls it directly, however, if I wrap it in a function as below, it all of a sudden works. Can anyone explain why though, because to me it seems like wrapping the call to a function inside a function should make no difference from just calling the function.
padArea.loadLayout(layoutName).then(function(result){
padArea.getButtonId('A0')
});
Strangely enough, as long as I wrap the second call in ( the one inside the .then ) my original code works as well.
Just use $http promise:
factory.loadLayout = function (layoutName) {
return $http.get('/api/getlayout/'+layoutName)
.success(function (data) {
layout = data;
console.log("Got " + layout.name);
resolve('OK');
})
.error(function(data,status,headers,config){
reject(new Error( status));
});
}
And then to use it...
factory.loadLayout(param).then(function (response) { ... });
OR
I see what you are trying to do. Instead create a deffered from $q
factory.loadLayout = function (layoutName) {
var defer = $q.defer();
$http.get('/api/getlayout/'+layoutName)
.success(function (data) {
layout = data;
console.log("Got " + layout.name);
defer.resolve('OK');
})
.error(function(data,status,headers,config){
defer.reject(new Error( status));
});
return defer.promise;
}
You could also use $q.defer():
deferred = $q.defer();
$http.get('/api/getlayout/'+layoutName)
.success(function (data) {
layout = data;
deferred.resolve("OK");
})
.error(function (data,status,headers,config){
deferred.reject(new Error(status));
})
return deferred.promise;
This can be a little more general if you're not just doing an http request, and lets you control the return.
I am learning AngularJS after converting from jQuery for a few years. And some bits are much more intuitive. Some not so much :).
I am trying to get my head around the use of promises, particularly $q in use with $http and there does not seem to be too much information around these two combined that I can find.
Why would I use promises in place of the success/error callback? They both make uses of callbacks in reality, so why is a promise considered better? E.g. I could set up a get(...) function like follows:
function get(url, success, error) {
success = success || function () {};
error = error || function () {};
$http.get(url)
.success(function (data) {
success(data);
})
.error(function (error) {
error(error);
});
}
get('http://myservice.com/JSON/',
function () {
// do something with data
},
function () {
// display an error
}
);
Which is good(?) because it gives me complete control over what is happening. If I call get(...) then I can control any success/errors wherever get is called.
If I convert this to use promises, then I get:
function get(url) {
return $http.get(url)
.then(function (data) {
return data;
},
function (error) {
return error;
});
}
get('http://myservice.com/JSON/')
.then(function (data) {
// do something with data
});
// cannot handle my errors?
Which is condensed, I agree; we also do not have to explicitly worry about the success/error callback, but I seem to have lost control over my error callback for a start - because I cannot configure a second callback to handle an error.
Which means that if I use this function in a service which can be used by multiple controllers, then I cannot update the UI to alert the user to an error.
Am I missing something? Is there a reason why promises is preferred? I cannot find an example why.
Usually you'll deal with asynchronous tasks in Javascript with callbacks;
$.get('path/to/data', function(data) {
console.log(data);
});
It works fine, but start to complicate when you go into whats called the 'callback hell';
$.get('path/to/data', function(data) {
$.get('path/to/data2' + data, function(data2) {
$.get('path/to/data3' + data2, function(data3) {
manipulate(data, data2, data3);
}, errorCb);
}, errorCb);
}, errorCb);
The alternative is working with promises and defered object;
Deferreds - representing units of work
Promises - representing data from those Deferreds
Sticking to this agenda can assist to you in every extreme asynctask case:
You have a regular call that need to get data from the server, manipulate it, and return to the scope
You have multiple calls that each is depending on the precious one (cahin strategy)
You want to send multiple (parallel) calls and handle their success in 1 block
You want your code to be orginized (prevent dealing with handling results on controllers)
Your task is the easiest one to handle with $q and $http
function get(url) {
var deferred = $q.defer();
$http.get(url)
.success(function (data) {
deferred.resolve(data);
})
.error(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
And calling the service function is the same
get('http://myservice.com/JSON/')
.then(function (data) {
// do something with data
});
// cannot handle my errors?
You can handle the error like this:
get('http://myservice.com/JSON/')
.then(function (data) {
// do something with data
},
function (error) {
//do something with error
});
But unfortunately since you have already caught the error then the final error won't be triggered. You will also have the same problem with success.
To get that to work you ned to use $q.
function get(url) {
var deferred = $q.defer();
$http.get(url)
.success(function (data) {
deferred.resolve(data);
})
.error(function (error) {
deferred.reject(error);
});
return deferred.promise;
}
Also there is no need to pass in success and error functions because you can use promises instead.
I would like to make multiple Ajax calls in a chain. But I also would like to massage the data after each call before making the next call. In the end, when All calls are successful, I would like to run some other code.
I am using Angular $http service for my Ajax calls and would like to stick to that.
Is it possible?
Yes, this is handled very elegantly by AngularJS since its $http service is built around the PromiseAPI. Basically, calls to $http methods return a promise and you can chain promises very easily by using the then method. Here is an example:
$http.get('http://host.com/first')
.then(function(result){
//post-process results and return
return myPostProcess1(result.data);
})
.then(function(resultOfPostProcessing){
return $http.get('http://host.com/second');
})
.then(function(result){
//post-process results of the second call and return
return myPostProcess2(result.data);
})
.then(function(result){
//do something where the last call finished
});
You could also combine post-processing and next $http function as well, it all depends on who is interested in the results.
$http.get('http://host.com/first')
.then(function(result){
//post-process results and return promise from the next call
myPostProcess1(result.data);
return $http.get('http://host.com/second');
})
.then(function(secondCallResult){
//do something where the second (and the last) call finished
});
The accepted answer is good, but it doesn't explain the catch and finally methods which really put the icing on the cake. This great article on promises set me straight. Here's some sample code based on that article:
$scope.spinner.start();
$http.get('/whatever/123456')
.then(function(response) {
$scope.object1 = response.data;
return $http.get('/something_else/?' + $scope.object1.property1);
})
.then(function(response) {
$scope.object2 = response.data;
if ($scope.object2.property88 == "a bad value")
throw "Oh no! Something failed!";
return $http.get('/a_third_thing/654321');
})
.then(function(response) {
$scope.object3 = response.data;
})
.catch(function(error) {
// this catches errors from the $http calls as well as from the explicit throw
console.log("An error occured: " + error);
})
.finally(function() {
$scope.spinner.stop();
});