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;
});
}
Related
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.
This question already has answers here:
How to know when all Promises are Resolved in a dynamic "iterable" parameter?
(5 answers)
Closed 6 years ago.
I've got an api call that sometimes returns paged responses. I'd like to automatically add these to my promises so I get the callback once all the data has arrived.
This is my attempt. I'd expect the new promise to be added and Promise.all to resolve once that is done.
What actually happens is that Promise.all doesn't wait for the second request. My guess is that Promise.all attaches "listeners" when it's called.
Is there a way to "reintialize" Promise.all()?
function testCase (urls, callback) {
var promises = [];
$.each(urls, function (k, v) {
promises.push(new Promise(function(resolve, reject) {
$.get(v, function(response) {
if (response.meta && response.meta.next) {
promises.push(new Promise(function (resolve, reject) {
$.get(v + '&offset=' + response.meta.next, function (response) {
resolve(response);
});
}));
}
resolve(response);
}).fail(function(e) {reject(e)});
}));
});
Promise.all(promises).then(function (data) {
var response = {resource: []};
$.each(data, function (i, v) {
response.resource = response.resource.concat(v.resource);
});
callback(response);
}).catch(function (e) {
console.log(e);
});
}
Desired flow is something like:
Create a set of promises.
Some of the promises spawn more promises.
Once all the initial promises and spawned promises resolve, call the callback.
It looks like the overall goal is:
For each entry in urls, call $.get and wait for it to complete.
If it returns just a response without "next", keep that one response
If it returns a response with a "next," we want to request the "next" as well and then keep both of them.
Call the callback with response when all of the work is done.
I would change #2 so you just return the promise and fulfill it with response.
A key thing about promises is that then returns a new promise, which will be resolved based on what you return: if you return a non-thenable value, the promise is fulfilled with that value; if you return a thenable, the promise is resolved to the thenable you return. That means that if you have a source of promises ($.get, in this case), you almost never need to use new Promise; just use the promises you create with then. (And catch.)
(If the term "thenable" isn't familiar, or you're not clear on the distinction between "fulfill" and "resolve", I go into promise terminology in this post on my blog.)
See comments:
function testCase(urls) {
// Return a promise that will be settled when the various `$.get` calls are
// done.
return Promise.all(urls.map(function(url) {
// Return a promise for this `$.get`.
return $.get(url)
.then(function(response) {
if (response.meta && response.meta.next) {
// This `$.get` has a "next", so return a promise waiting
// for the "next" which we ultimately fulfill (via `return`)
// with an array with both the original response and the
// "next". Note that by returning a thenable, we resolve the
// promise created by `then` to the thenable we return.
return $.get(url + "&offset=" + response.meta.next)
.then(function(nextResponse) {
return [response, nextResponse];
});
} else {
// This `$.get` didn't have a "next", so resolve this promise
// directly (via `return`) with an array (to be consistent
// with the above) with just the one response in it. Since
// what we're returning isn't thenable, the promise `then`
// returns is resolved with it.
return [response];
}
});
})).then(function(responses) {
// `responses` is now an array of arrays, where some of those will be one
// entry long, and others will be two (original response and next).
// Flatten it, and return it, which will settle he overall promise with
// the flattened array.
var flat = [];
responses.forEach(function(responseArray) {
// Push all promises from `responseArray` into `flat`.
flat.push.apply(flat, responseArray);
});
return flat;
});
}
Note how we never use catch there; we defer error handling to the caller.
Usage:
testCase(["url1", "url2", "etc."])
.then(function(responses) {
// Use `responses` here
})
.catch(function(error) {
// Handle error here
});
The testCase function looks really long, but that's just because of the comments. Here it is without them:
function testCase(urls) {
return Promise.all(urls.map(function(url) {
return $.get(url)
.then(function(response) {
if (response.meta && response.meta.next) {
return $.get(url + "&offset=" + response.meta.next)
.then(function(nextResponse) {
return [response, nextResponse];
});
} else {
return [response];
}
});
})).then(function(responses) {
var flat = [];
responses.forEach(function(responseArray) {
flat.push.apply(flat, responseArray);
});
return flat;
});
}
...and it'd be even more concise if we were using ES2015's arrow functions. :-)
In a comment you've asked:
Could this handle if there was a next next? Like a page 3 of results?
We can do that by encapsulating that logic into a function we use instead of $.get, which we can use recursively:
function getToEnd(url, target, offset) {
// If we don't have a target array to fill in yet, create it
if (!target) {
target = [];
}
return $.get(url + (offset ? "&offset=" + offset : ""))
.then(function(response) {
target.push(response);
if (response.meta && response.meta.next) {
// Keep going, recursively
return getToEnd(url, target, response.meta.next);
} else {
// Done, return the target
return target;
}
});
}
Then our main testCase is simpler:
function testCase(urls) {
return Promise.all(urls.map(function(url) {
return getToEnd(url);
})).then(function(responses) {
var flat = [];
responses.forEach(function(responseArray) {
flat.push.apply(flat, responseArray);
});
return flat;
});
}
Assuming you are using jQuery v3+ you can use the promises returned by $.ajax to pass to Promise.all().
What you are missing is returning the second request as a promise instead of trying to push it to the promises array
Simplified example
var promises = urls.map(function(url) {
// return promise returned by `$.ajax`
return $.get(url).then(function(response) {
if (response.meta) {
// return a new promise
return $.get('special-data.json').then(function(innerResponse) {
// return innerResponse to resolve promise chain
return innerResponse;
});
} else {
// or resolve with first response
return response;
}
});
})
Promise.all(promises).then(function(data) {
console.dir(data)
}).catch(function(e) {
console.log(e);
});
DEMO
I have an app using IndexedDB. Originally I made prolific use of callbacks, and decided to clean it up using angularjs promises $q.
FAIL.
http://jsfiddle.net/ed4becky/bumm337e/
angular.module("IDBTest", []);
angular.module("IDBTest")
.service("initSvc", ['$q', function ($q) {
var svc = this;
svc.dbVersion = 1;
svc.open = open;
svc.deleteDB = deleteDB;
var idb = window.indexedDB;
function deleteDB() {
return $q(function (resolve, reject) {
idb.webkitGetDatabaseNames().onsuccess =
function (sender, args) {
if (sender.target.result
&& sender.target.result.length > 0) {
var db = sender.target.result[0];
console.log("deleting " + db);
var request = idb.deleteDatabase(db);
request.onsuccess = function () {
console.log('database ' + db + ' deleted.');
resolve();
};
request.onerror = function () {
reject();
};
} else {
console.log("Nothing to delete");
resolve();
};
};
});
}
function open(dbName) {
return $q(function (resolve, reject) {
var request = idb.open(dbName, svc.dbVersion);
request.onupgradeneeded = function (e) {
var db = e.target.result;
console.log("creating new " + db.name);
e.target.transaction.onerror = function (e) {
console.log(e);
};
db.createObjectStore("table1", {
keyPath: "id"
});
db.createObjectStore("table2", {
keyPath: "id"
});
db.createObjectStore("table3", {
keyPath: "id"
});
};
request.onsuccess = function (e) {
console.log('database ' + dbName + ' open.');
svc.db = e.target.result;
resolve();
};
request.onerror = function () {
reject();
};
});
}
}]);
angular.module('IDBTest')
.factory('$exceptionHandler', ['$log', function ($log) {
return function (exception, cause) {
throw exception;
};
}]);
angular.module('IDBTest')
.run(['initSvc', function (initSvc) {
initSvc.deleteDB()
.then(initSvc.open('testDatabase'))
.then(function () {
console.log(initSvc.db.name + ' initialized');
});
}]);
This fiddle shows my expectation that
Any databases created are deleted.
then
A database is open triggering an onupgradeneeded
then
The database is referenced
Unfortunately the then statments seem to get called BEFORE the promises are resolved in the onsuccess methods of the IDB calls.
To recreate, run the jsfiddle with the console open. May have to run it a couple times to get the exception, but it fails most times, because the last then clause is called before the onsuccess on the database open is called.
Any ideas?
I believe the issue is with the Promise Chain. The documentation from Angular is a bit confusing, but it seems as though if the return value of the callback method is a promise, it will resolve with a value; not a promise. Thus, breaking the chain.
From Angular Promise 'Then' Method Documentation:
then(successCallback, errorCallback, notifyCallback) – regardless of
when the promise was or will be resolved or rejected, then calls one
of the success or error callbacks asynchronously as soon as the result
is available. The callbacks are called with a single argument: the
result or rejection reason. Additionally, the notify callback may be
called zero or more times to provide a progress indication, before the
promise is resolved or rejected.
This method returns a new promise which is resolved or rejected via
the return value of the successCallback, errorCallback (unless that
value is a promise, in which case it is resolved with the value which
is resolved in that promise using promise chaining). It also notifies
via the return value of the notifyCallback method. The promise cannot
be resolved or rejected from the notifyCallback method.
I'm able to get it to initialize with this:
angular.module('IDBTest')
.run(['initSvc', function (initSvc) {
initSvc.deleteDB()
.then(function() {
return initSvc.open('testDatabase');
})
.then(function () {
console.log(initSvc.db.name + ' initialized');
});
}]);
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
I am using the when library with Node js. I create a deffered object, place the resolve inside an encapsulated Mongoose findOne() function, and return the promise outside. But it seems my promise is always returned before the data is retrieved.
User.prototype.getProfile = function(criteria) {
var deferred = when.defer();
var options = {
criteria: criteria,
select: 'name id email'
};
this.User.load(options, function(err, data) {
if (data) {
this.name = data.name;
this.email = data.email;
this.id = data.id;
} else {
return false;
}
console.log(data);
deferred.resolve();
});
console.log('returning promise');
return deferred.promise;
};
Caller
User.getProfile(req.query).then(
function success(data) {
res.send('Hello ' + User.name);// Hello ''
}
);
Outputs 'returning promise' before the data
Yes, promise will be returned to the caller instead of the data and that is how we can take advantage of the asynchronous functions. This is the common sequence of actions in handling async calls,
Make an async call.
Return a Promise to the caller.
At this point, caller doesn't have to wait for the result. It can simply define a then function, which knows what to do when the data is ready and move on to the next task.
Later point of time, resolve (or reject, if failed) the promise when you get the result from the async call.
Execute the then function on the Promise object, with the result from the async call.
So, your code will have to be modified a little bit, like this
User.prototype.getProfile = function(criteria) {
var deferred = when.defer();
var options = {
criteria: criteria,
select: 'name id email'
};
this.User.load(options, function(err, data) {
if (err) {
// Reject, if there is an error
deferred.reject(err);
} else {
// Resolve it with actual data
deferred.resolve(data);
}
});
return deferred.promise;
};
Then your caller will do something like this
userObject.getProfile()
.then(function(profileObject) {
console.log(profileObject);
// Do something with the retrieved `profileObject`
})
.catch(function(err) {
console.err("Failed to get Profile", err);
});
// Do something else here, as you don't have to wait for the data
Here, caller just calls getProfile and attaches a function which says what to do with the returned data and moves on.
Edit If you want the same object to be updated, then you can simply use similar code, but you need to preserve this in some other variable, because the binding of this happens at runtime.
User.prototype.getProfile = function(criteria) {
var deferred = when.defer();
var options = {
criteria: criteria,
select: 'name id email'
};
var self = this;
this.User.load(options, function(err, data) {
if (err) {
// Reject, if there is an error
deferred.reject(err);
} else {
self.name = data.name;
self.email = data.email;
self.id = data.id;
}
deferred.resolve(data);
});
return deferred.promise;
};
That's how promises work.
Since you have an async task that takes some time, and JavaScript is a single threaded language, you don't want to block your code and wait for that async operation to complete itself - otherwise nobody would use JavaScript!!
So what do you do? You create a promise and continue your code.
You add callbacks to that promise and when the promise is resolved your callbacks are invoked.
I didn't use the when library but what you want to do is something like this:
User.prototype.getProfile = function(criteria){
var deferred = when.defer();
var options = {
criteria : criteria,
select : 'name id email'
};
this.User.load(options, function(err, data) {
if (data) {
this.name = data.name;
this.email = data.email;
this.id = data.id;
console.log(data);
// the callback will invoke after the deferred object is resolved.
deferred.promise.then(function(o){ console.log('resolved!!!'); });
deferred.resolve(data);
}else{
deferred.reject('something bad occured');
return false;
}
});
return deferred.promise;
};