Return a promise of a promise - javascript

I'm quite new to Angular, but I'm trying to find out some things.
I do have a method which returns a promise:
preloaderServiceObject.Load = function(referencePaths){
var deferred = $q.defer();
$(referencePaths).each(function(index, referencePath) {
var preloadedElement = document.createElement('img');
{
preloadedElement.onload = deferred.resolve;
preloadedElement.src = referencePath;
}
});
return deferred.promise;
}
This is all working fine and doesn't cause the problem.
However, I do have another method which should return a promise inside the completion call of the promise, like so:
OfficeUIRibbonControlServiceObject.Initialize = function(configurationFile) {
$http.get(configurationFile)
.then(function (response) {
$rootScope.Tabs = response.data.Tabs;
$rootScope.ContextualGroups = response.data.ContextualGroups;
var images = JSPath.apply('.Groups.Areas.Actions.Resource', $rootScope.Tabs);
images.concat(JSPath.apply('.Tabs.Groups.Areas.Actions.Resource', $rootScope.ContextualGroups));
PreloaderService.Load(images);
});
}
The last line PreloaderService.Load(images); does return a promise as defined in the first function in this post.
But, now I want to call the method `OfficeUIRibbonControlServiceObject.Initialize', but how should i change this method so that I can wait for until the loading of the PreloaderService has been completed?
Just changing the method to return that promise will not work, because the returned object will be undefined (since I'm in the then method of the $http.
Kind regards,
Edit: As suggested by Rouby, using a promise:
The initialize function:
OfficeUIRibbonControlServiceObject.Initialize = function(configurationFile) {
$http.get(configurationFile)
.then(function (response) {
$rootScope.Tabs = response.data.Tabs;
$rootScope.ContextualGroups = response.data.ContextualGroups;
var images = JSPath.apply('.Groups.Areas.Actions.Resource', $rootScope.Tabs);
images.concat(JSPath.apply('.Tabs.Groups.Areas.Actions.Resource', $rootScope.ContextualGroups));
var deferred = $q.defer();
PreloaderService.Load(images).then(function() {
deferred.resolve();
});
return deferred;
});
}
The InitializeService method:
function InitializeService(serviceInstance, configurationFile) {
serviceInstance.Initialize(configurationFile).then(function() {
console.log('This method has been called.');
});
}
The result of this is that I get: Error: serviceInstance.Initialize(...) is undefined

Create a new deferred in .Initialize that gets resolved when the second .Load finishes, you can then return this deferred as normal.
E.g.
PreloaderService.Load(images).then(function(){ newDeferred.resolve(); }, function(){ newDeferred.reject(); });

Better return the promise:
OfficeUIRibbonControlServiceObject.Initialize = function(configurationFile) {
return $http.get(configurationFile)
.then(function (response) {
$rootScope.Tabs = response.data.Tabs;
$rootScope.ContextualGroups = response.data.ContextualGroups;
var images = JSPath.apply('.Groups.Areas.Actions.Resource', $rootScope.Tabs);
images.concat(JSPath.apply('.Tabs.Groups.Areas.Actions.Resource', $rootScope.ContextualGroups));
return PreloaderService.Load(images);
});
}
When you now call the OfficeUIRibbonControlServiceObject.Initialize function the result from PreloaderService.Load will be returned.
example:
OfficeUIRibbonControlServiceObject.Initialize(// myConfiguration //).then (
function success (response) {
console.log("promise success", response)
},
function fail (error) {
console.log("promise fail", error) // the result from PreloaderService.Load
}
);
In general: you can return values or promises in the .then function. When you return a promise. The resolve value of that promise will be returned after that promise is resolved

Related

Angular 1.x, How to resolve parent's promise from callback

I am trying to wrap my post/get/put/delete calls so that any time they are called, if they fail they will check for expired token, and try again if that is the reason for failure, otherwise just resolve the response/error. Trying to avoid duplicating code four times, but I'm unsure how to resolve from a non-anonymous callback.
factory.post = function (url, data, config) {
var deferred = $q.defer();
$http.post(url, data, config).then(factory.success, factory.fail);
return deferred.promise;
}
factory.success = function (rsp) {
if (rsp) {
//how to resolve parent's promise from from here
}
}
Alternative is to duplicate this 4 times:
.then(function (rsp) {
factory.success(rsp, deferred);
}, function (err) {
factory.fail(err, deferred);
});
One solution might be using bind function.
function sum(a){
return a + this.b;
}
function callFn(cb){
return cb(1);
}
function wrapper(b){
var extra = {b: b};
return callFn(sum.bind(extra));
}
console.log(wrapper(5));
console.log(wrapper(-5));
console.log(wrapper(50));
For your solution check bellow example
factory.post = function (url, data, config) {
var deferred = $q.defer();
$http.post(url, data, config).then(factory.success.bind({deferred: deferred}), factory.fail.bind({deferred: deferred}));
return deferred.promise;
}
factory.success = function (rsp) {
if (rsp) {
this.deferred.resolve(rsp);
//how to resolve parent's promise from from here
}else {
//retry or reject here
}
}
From what I understand, you just want to resolve the deferred object on success and retry on error in case of expired token. Also you probably want to keep a count of number of retries. If so,
Edit - Seems I misunderstood the question. The answer suggested by Atiq should work, or if you are using any functional JS libraries like underscore or Ramdajs, you could use curry function. Using curry function, you can pass some parameters to the function and the function will get executed only after all the parameters are passed. I have modified the code snippet to use curry function from underscorejs.
factory.post = function (url, data, config) {
var deferred = $q.defer();
$http.post(url, data,
config).then(_.curry(factory.success(deferred)),
_.curry(factory.fail(deferred));
return deferred.promise;
}
factory.success = function (deferred, rsp) {
if (rsp) {
//handle resp
deferred.resolve(rsp);
}
}
factory.fail = function(deferred, err){
//handle retry
deferred.reject(err);
}

Unit Testing of chain of promises with Jasmine

I want to write the unit test for the factory which have lot chain of promises. Below is my code snippet:
angular.module('myServices',[])
.factory( "myService",
['$q','someOtherService1', 'someOtherService2', 'someOtherService3', 'someOtherService4',
function($q, someOtherService1, someOtherService2, someOtherService3, someOtherService4) {
method1{
method2().then(
function(){ someOtherService3.method3();},
function(error){/*log error;*/}
);
return true;
};
var method2 = function(){
var defer = $q.defer();
var chainPromise = null;
angular.forEach(myObject,function(value, key){
if(chainPromise){
chainPromise = chainPromise.then(
function(){return method4(key, value.data);},
function(error){/*log error*/});
}else{
chainPromise = method4(key, value.data);
}
});
chainPromise.then(
function(){defer.resolve();},
function(error){defer.reject(error);}
);
return defer.promise;
};
function method4(arg1, arg2){
var defer = $q.defer();
someOtherService4.method5(
function(data) {defer.resolve();},
function(error) {defer.reject(error);},
[arg1,arg2]
);
return defer.promise;
};
var method6 = function(){
method1();
};
return{
method6:method6,
method4:method4
};
}]);
To test it, I have created spy object for all the services, but mentioning the problematic one
beforeEach( function() {
someOtherService4Spy = jasmine.createSpyObj('someOtherService4', ['method4']);
someOtherService4Spy.method4.andCallFake(
function(successCallback, errorCallback, data) {
// var deferred = $q.defer();
var error = function (errorCallback) { return error;}
var success = function (successCallback) {
deferred.resolve();
return success;
}
return { success: success, error: error};
}
);
module(function($provide) {
$provide.value('someOtherService4', someOtherService4);
});
inject( function(_myService_, $injector, _$rootScope_,_$q_){
myService = _myService_;
$q = _$q_;
$rootScope = _$rootScope_;
deferred = _$q_.defer();
});
});
it("test method6", function() {
myService.method6();
var expected = expected;
$rootScope.$digest();
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
});
It is showing error on
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
After debugging I found that it is not waiting for any promise to resolve, so method 1 return true, without even executing method3. I even tried with
someOtherService4Spy.method4.andReturn(function(){return deferred.promise;});
But result remain same.
My question is do I need to resolve multiple times ie for each promise. How can I wait till all the promises are executed.
method1 does not return the promise so how would you know the asynchrounous functions it calls are finished. Instead you should return:
return method2().then(
method6 calls asynchronous functions but again does not return a promise (it returns undefined) so how do you know it is finished? You should return:
return method1();
In a test you should mock $q and have it resolve or reject to a value but I can't think of a reason why you would have a asynchronous function that doesn't return anything since you won't know if it failed and when it's done.
Method 2 could be written in a more stable way because it would currently crash if the magically appearing myObject is empty (either {} or []
var method2 = function(){
var defer = $q.defer();
var keys = Object.keys(myObject);
return keys.reduce(
function(acc,item,index){
return acc.then(
function(){return method4(keys[index],myObject[key].data);},
function(err){console.log("error calling method4:",err,key,myObject[key]);}
)
}
,$q.defer().resolve()
)
};
And try not to have magically appearing variables in your function, this could be a global variable but your code does not show where it comes from and I doubt there is a need for this to be scoped outside your function(s) instead of passed to the function(s).
You can learn more about promises here you should understand why a function returns a promise (functions not block) and how the handlers are put on the queue. This would save you a lot of trouble in the future.
I did below modification to get it working. I was missing the handling of request to method5 due to which it was in hang state. Once I handled all the request to method 5 and provided successCallback (alongwith call to digest()), it started working.
someOtherService4Spy.responseArray = {};
someOtherService4Spy.requests = [];
someOtherService4Spy.Method4.andCallFake( function(successCallback, errorCallback, data){
var request = {data:data, successCallback: successCallback, errorCallback: errorCallback};
someOtherService4Spy.requests.push(request);
var error = function(errorCallback) {
request.errorCallback = errorCallback;
}
var success = function(successCallback) {
request.successCallback = successCallback;
return {error: error};
}
return { success: success, error: error};
});
someOtherService4Spy.flush = function() {
while(someOtherService4Spy.requests.length > 0) {
var cachedRequests = someOtherService4Spy.requests;
someOtherService4Spy.requests = [];
cachedRequests.forEach(function (request) {
if (someOtherService4Spy.responseArray[request.data[1]]) {
request.successCallback(someOtherService4Spy.responseArray[request.data[1]]);
} else {
request.errorCallback(undefined);
}
$rootScope.$digest();
});
}
}
Then I modified my test as :
it("test method6", function() {
myService.method6();
var expected = expected;
var dataDict = {data1:"data1", data2:"data2"};
for (var data in dataDict) {
if (dataDict.hasOwnProperty(data)) {
someOtherService4Spy.responseArray[dataDict[data]] = dataDict[data];
}
}
someOtherService4Spy.flush();
expect(someOtherService3.method3.mostRecentCall.args[0]).toEqualXml(expected);
expect(someOtherService4Spy.method4).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function), [arg,arg]);
});
This worked as per my expectation. I was thinking that issue due to chain of promises but when I handled the method5 callback method, it got resolved. I got the idea of flushing of requests as similar thing I was doing for http calls.

Multiple jQuery promises in sequential order

Basically I want this:
function do_ajax_calls(...){
var d = $.Deferred();
$.ajax(args).done(function(){
$.ajax(args).done(function(){
$.ajax(args).done(function(){
d.resolve();
});
});
})
return d.promise();
}
But the number of ajax calls depends on the arguments that I pass to the function, which is an array, so I can't use that code.
The function should return a promise that only resolves when the last ajax calls completes. So the function needs to be called like this:
do_ajax_calls(....).done(function(){
// here is the callback
})
Does anyone know how can I do this?
But the number of ajax calls depends on the arguments that I pass to the function, which is an array
If it's one ajax call per array item
function do_ajax_calls(args) {
return args.reduce(function(promise, item) {
return promise.then(function() {
return $.ajax(args); // should that be item?
});
}, Promise.resolve(true));
}
The Promise.resolve(true) is a "native" promise, i.e. not available in IE, but I'm sure jQuery has an equivalent
Here's a JSFiddle Demo
One of the reasons promises are a big deal is because they can be chained. You can use this to your advantage to iteratively chain additional requests onto the resolution of the previous one:
function do_ajax_calls() {
var dfd = $.Deferred();
var promise = dfd.promise();
var responses = [];
function chainRequest(url) {
promise = promise.then(function (response) {
responses.push(response);
return $.ajax(url, { method: 'POST' });
});
}
for (var i = 0, length = arguments.length; i < length; i++) {
chainRequest(arguments[i]);
}
dfd.resolve();
return promise.then(function (response) {
return responses.slice(1).concat(response);
});
}
The above code will return a promise ultimately resolving to an array of all of the responses. If any one of the requests fails, the promise will reject with the first failure.
JSFiddle
Here is it Demo
var counter = 1 ;
function multipleAjax(loop)
{
if(counter<loop)
{
$.ajax(
{
url: 'http://mouadhhsoumi.tk/echo.php',
success:function(data)
{
multipleAjax(loop);
$(".yo").append(data+"</br>");
counter++;
}
});
}
}
multipleAjax(5);
Try using $.when() , Function.prototype.apply() , $.map()
function do_ajax_calls(args) {
return $.when.apply($, $.map(args, function(request, i) {
return $.ajax(request) // `request` : item with `args` array
}))
}
do_ajax_calls
.then(function success() {
console.log(arguments)
}, function err() {
console.log("err", arguments)
});

Function doesn't return $http response

I have created a function expression and assigned it to scope, the idea being that the function will initiate an $http request, get a property and then return it.
$scope.getRequestDigest = function () {
var url = urlParams['SPAppWebUrl'] + '/_api/contextinfo';
$http.post(url)
.success(function (res) {
return res;
});
}
However when I call $scope.getRequestDigest() it simply returns undefined, presumably because the ajax call hasn't completed yet. Is there any way to delay the return until the $http request is complete? I've tried using the .success() promise but that doesn't seem to work.
$http.post returns a promise (see $q). In order to use the result, bind res to $scope.res:
controller:
$scope.getRequestDigest = function () {
var url = urlParams['SPAppWebUrl'] + '/_api/contextinfo';
$http.post(url)
.success(function (res) {
$scope.res = res;
});
}
Then, you can use $scope.res (or res in the template) anywhere you'd like.
After the promise chain is resolved (after success), Angular will run a digest cycle and rebind everything on $scope.
Try
$scope.getRequestDigest = function () {
var url = urlParams['SPAppWebUrl'] + '/_api/contextinfo';
return $http.post(url);
}
var digestPromise = $scope.getRequestDigest();
digestPromise.then(function(response){
console.log(response.data);
});
This way you are actually returning a promise, which AngularJS implements through the $q service.
If you were to output (console.log(digestPromise)) digestPromise, you will see that you can all sorts of functions on it, like success or complete, for example.
You could use chain promise using .then
$scope.getRequestDigest = function () {
var url = urlParams['SPAppWebUrl'] + '/_api/contextinfo';
return $http.post(url) //this will return a promise
.then(function (res) {
return res.data; //on success this will return a data to caller function
});
}
Then the caller function will have call the function and get the data like this
$scope.getRequestDigest().then(function(data){
console.log(data)
//here you can get data returned from `getRequestDigest` method
})

Returning the output of a promise

I am trying to return the output of a promise. However, I am just getting the actual promise, instead of the value.
What am I doing wrong?
Here's my code:
$rootScope.distance = function(lon1, lat1) {
var deferred = $q.defer();
$cordovaGeolocation.getCurrentPosition()
.then(function (position) {
deferred.resolve(position);
}, function(err) {
});
return deferred.promise;
}
...and the result
Since a promise is asynchronous, you can't get its value directly (synchronous) after calling the promise returning function. You could do this
$rootScope.updateDistance = function(lon1, lat1) {
$cordovaGeolocation.getCurrentPosition()
.then(function (position) {
$rootScope.distance = position;
});
}

Categories