I'm a bit new to $q angular promises. Here is my code structure:
$q.all(promises).then(function(results) {
results.forEach(function(data, status, headers, config) {
console.log(status);
});
$scope.saveDocumentCaseState();
}, function errorCallBack(response) {
console.log('error while mass update case entries');
console.log(response);
$scope.submitToWsSuccessful = 2;
});
Is there a possibility to access the result of each promise when it has been executed before the next one?
So assuming that you receive an array with an arbitrary number of promises from an angular factory called promiseFactory:
var promises = promiseFactory.getPromises(); // Returns an array of promises
promises.forEach(function(p){
p.then(promiseSuccess, promiseError);
function promiseSuccess(){
// Do something when promise succeeds
}
function promiseError(){
// Do something when promise errors
}
});
$q.all(promises).then(allSuccess, allError);
function allSuccess(){
// All calls executed successfully
}
function allError(){
// At least one of the calls failed
}
Related
I work on my angularjs project.
I created this service:
(function () {
"use strict";
angular.module("manageItems").factory("manageItemsService", ["$http", "config", manageItemsService]);
function manageItemsService($http, config) {
var service = {
getNewItems: getNewItems,
};
return service;
function getNewItems(session, mapName) {
return $http.get(serviceUrl + 'getNewItems/' + session + "/" + mapName);
}
}
})();
And here how I call the service from controller:
function getNewItems() {
manageItemsService.getNewItems(mapguideService.mapName, mapguideService.sessionId).then(function (result) {
self.currentItems = result.data;
})
}
I need to make service to delay while the response returned.
How can I change servicefunction to make it wait until self.currentItems property is populated by data?
First I need say that http requests are actually performed asynchronously so as not to stop the application while the result is returned.
So you have two options, use the angular pattern to adjust your method in order to treat the result, so you must pass a callback function to the service, so that the service, not the controller make the association. It would be something like:
Service:
(function () {
"use strict";
angular.module("manageItems").factory("manageItemsService", ["$http", "config", manageItemsService]);
function manageItemsService($http, config) {
var service = {
getNewItems: getNewItems,
};
return service;
function getNewItems(session, mapName, callback, errorCallback) {
$http.get(serviceUrl + 'getNewItems/' + session + "/" + mapName).then(callback, errorCallback);;
}
}
})();
Controller:
function getNewItems() {
manageItemsService.getNewItems(mapguideService.mapName, mapguideService.sessionId, function (result) {
//this callback will be called asynchronously when the response is available
self.currentItems = result.data;
}, function(error) {
// called asynchronously if an error occurs
// or server returns response with an error status.
})
}
The second option is to totally not recommended, inserting a loop while the result is expected... (to bad)
I hope I have helped!
Then you could be put up .then on getNewItems $http call. And based on retrieved response data, decide whether to return data or call another service method.
function anotherFunction(){
return $http.get(url);
}
function getNewItems(session, mapName) {
return $http.get(serviceUrl + 'getNewItems/' + session + "/" + mapName).then(function successCallback(response){
var data = response.data;
//call another function if data is empty
if(!data.length)
return anotherFunction(); //make sure another function should return promise
return data;
});
}
What the code needs to do is chain promises.
To make the getNewItems function chainable, return the derived promise:
function getNewItems() {
//vvvv RETURN promise
return manageItemsService.getNewItems(mapguideService.mapName, mapguideService.sessionId)
.then(function (response) {
self.currentItems = response.data;
//RETURN value to chain
return response.data;
});
};
Then use the returned promise to chain more operations:
getNewItems().then( function(currentItems) {
//Evaluate current Items
if ( ok ) {
return "DONE";
} else {
//RETURN to chain something else
return getOtherItems();
};
}).then( function(otherItems) {
if (otherItems == "DONE") return;
//ELSE
self.otherItems = otherItems;
//Do further chaining
});
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises.
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.
— AngularJS $q Service API Reference - Chaining Promises
In all my services, I'm just invoking REST services and returning the promises to the controllers. All the error's are handled at controllers using catch like below,
MyService.getData(url).then(getDataSuccess).catch(exception.catcher('Contact Admin : '));
My question here is, Since the real $http calls will be made at service, should I have to write catchers in service or catching in controller is fine?,
Scenario 1:
function getData(url){
return $http.get(url);
}
Scenario 2: (Nested calls to make combined results)
function getOtherData(url){
var defer = $q.defer();
$http.get(url).then(
function(response){
$http.get(nextService).then(
function(res){
defer.resolve('combined data');
}
)
}
);
return defer.promise;
}
Both the service method is not handling any errors. Instead it just returns the promise. Will there be any situation where this kind of exception handling will get failed?
Note: I have created decorators for handling javascript,angular errors and route errors separately. This question is particularly about $http service errors.
Yes what you have can fail triggering your catch because you have no reject().
You are using an anti-pattern creating your own promise and not chaining the nested request properly. Neither of those request rejections will be returned anywhere.
To be able to chain these get rid of the $q.defer() and do:
function getOtherData(url) {
// return beginning of promise chain
return $http.get(url).then(function (response) {
// return next promise
return $http.get(nextService).then(function (res) {
// combine and return the data
return {
d1 : response.data,
d2 : res.data
};
});
});
}
Now walk through the scenarios and each part of chain is intact.
Think of the chain as each then needs a return until the end of the chain
Scenario 2: (Nested calls to make combined results)
Failed Scenario
function getOtherData(url){
var defer = $q.defer();
$http.get(url).then(
function(response){
$http.get(nextService).then(
function(res){
defer.resolve('combined data');
}
)
}
);
return defer.promise;
}
This scenario will fail if the first $http.get has an error. The promise will hang and never get resolved. This is why we recommend avoiding using $q.defer to create promises from services that already return promises.
Instead return data and chain promises.
function getOtherData(url) {
var promise = $http.get(url);
var derivedPromise =
promise.then ( function (response) {
var data = response.data;
var nextPromise = $http.get(nextService);
var derivedNext = nextPromise.then(function(response) {
//return for chaining
return response.data;
});
//return composite for chaining
return $q.all([data, derivedNext]);
});
return derivedPromise;
};
The getOtherData(url) promise will be fulfilled with an array with the data from the two XHRs or it will be rejected with the first error response.
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1
Chaining error handlers
In an error handler, to convert a rejected resolution to a fulfilled resolution return data. To chain a rejection, throw the error.
For example:
promise = http.get(someUrl);
derivedPromise = promise.catch(function(errorResponse) {
if (fixable) {
fixedPromise = $http.get(fixedUrl);
//return to convert
return fixedPromise;
} else {
//throw to chain rejection
throw errorResponse;
}
};
By chaining error handlers, errors can be handled both by the service and the client of the service.
This makes it possible to implement powerful APIs like $http's response interceptors.1
Building on #georgeawg's answer, if you want to return multiple sets of data then you don't need nested calls.
function getOtherData(url) {
var promise1 = $http.get(url).then ( function (response) {
return response.data;
});
var promise2 = $http.get(nextService).then(function(response) {
return response.data;
});
return $q.all([promise1, promise2]);
};
Now the caller gets a promise that resolves to a list of the 2 data items (or is rejected if either request fails). The only real difference is that both requests are issues in parallel.
This generalises easily to a situation where you could have a list of urls, fetch them all in parallel and get an array of the response.data items.
Because you get back only a single promise that resolves to an array of data you can handle the result in the controller, but you only need one error handler.
MyService.getOtherData(url)
.then(getDataSuccess)
.catch(exception.catcher('Contact Admin : '));
Although the original question doesn't specify, it might be the case that the second url depends on the result from the first. You can handle that case here as well if you remember that you can call .then() multiple times on the same promise:
function getOtherData(url) {
var promise1 = $http.get(url).then ( function (response) {
return response.data;
});
var promise2 = promise1.then(function(response) {
// compute nextService from response.data here...
var nextService = foo(response.data);
return $http.get(nextService).then(function(response) {
return response.data;
});
});
return $q.all([promise1, promise2]);
};
I have the following function in my controller.
RestRequestsSrvc.getUserDetail()
.then(
function (response) {
$scope.user.userDetail = response;
},
function (error) {
// TODO
});
If I type
console.log(RestRequestsSrvc.getUserDetail());
the console logs a promise. I want to set a variable the the response. How can I modify my code so that I get the response instead of a promise?
Return a promise because your request is async.
You should wait the response,
Putting the console.log inside the callback function should print your info.
RestRequestsSrvc.getUserDetail()
.then(
function (response) {
$scope.user.userDetail = response;
console.log(response);
},
function (error) {
// TODO
});
You can do the console.log into the promise .then
RestRequestsSrvc.getUserDetail()
.then(
function (response) {
$scope.user.userDetail = response;
console.log(response);
},
function (error) {
// TODO
});
The thing is that when you call the function it will be executed but will not wait for the result, that's why you get a promise. The .then stuff is called once the request is done executing. That's where you handle your success or error callbacks.
Requests to the server are asynchronous, meaning that you must handle the response inside the callback.
You could use async false flag but this is not recommended if you have independent modules that executed later in the code.
I am still trying to learn Jasmine and test an Angular service. Currently I am trying to test the .success and error calls off http.get.
Service Call
this.get = function (param1, param2) {
return $http.get('api/something/get/param1/param2')
.success(function (data) {
return data;
})
.error(function() {
return "Please select param1 AND param2";
});
};
Jasmine Tests
it('service makes unsuccessful API call', function() {
var response = "This is the response";
httpBackend.when('GET', "api/something/Get/0/0").respond(404);
var data;
service.get(0, 0).then(function(result) {
data = result;
});
httpBackend.flush();
expect(data).toEqual("Please select param1 AND param2");
});
it('service makes successful API call', function () {
var response = "This is the response";
httpBackend.when('GET', "api/something/Get/0/0").respond(response);
var data;
service.get(0, 0).then(function(result) {
data = result.data;
});
httpBackend.flush();
expect(data).toEqual(response);
});
In the first test (Error) the data = result.data line in then() is never called. On the expect(data).toEqual(), data is undefined. When I step through everything I see where the service is called and the error message is populated in result.data.
In the second test (Success), I see the same thing but the data is set when the then function is called.
Why is my then function not called on .error()?
success() and error() don't work the same way as then(). The value returned by the callback is ignored. success() and error() return the promise on which they're called, and not a new promise like then().
So, their usage should be limited to callbacks having side-effects only (like initializing a scope variable).
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.