I can't seem to figure to figure out why after using promises it still isn't waiting and so $scope.return_message is returning null.
function getRandomCommit()
{
var d = $q.defer();
$scope.repos = Repos.query({username: 'octocat'}).$promise.then(function ( value )
{
var ranNum = Math.floor((Math.random()*value.length) + 1);
$scope.repo = Repo.get({username: 'octocat'}, {repo: value[ranNum].name}).$promise.then(function ( value2 )
{
$scope.commits = Commit.query({username: 'octocat'}, {repo: value2.name}).$promise.then(function ( value3 )
{
var ranNum2 = Math.floor((Math.random()*value3.length));
d.resolve(value3[ranNum2].commit.message);
return d.promise;
});
});
});
};
$scope.return_message = getRandomCommit();`
AngularJS 1.1.x and 1.0.x contained somewhat experimental feature called unwrapping promises. You could pass a promise to the template and once it got resolved, the template would automatically use the resolved value instead of the promise object itself.
Pretty cool, huh? It may sound like a nice feature but in fact it wasn't that useful after all. It added too much logic to AngularJS, slowed down evaluation and made code in templates/controllers unclear. AngularJS crew member commented on GitHub:
This is a feature that didn't prove to be wildly useful or popular,
primarily because of the dichotomy between data access in templates
(accessed as raw values) and controller code (accessed as promises).
In most code we ended up resolving promises manually in controllers
or automatically via routing and unifying the model access in this way.
Therefore it was deprecated in AngularJS 1.2.x.
Your options now are:
wait for the promise to resolve and set the variable on $scope
force enabling unwrapping promises (deprecated in 1.2 and removed in 1.3 so it's not really an option anymore!)
To make your code work refactor getRandomCommit to return a promise:
function getRandomCommit () {
var d = $q.defer();
// your code ...
return d.promise;
}
and change what $scope.return_message is:
getRandomCommit().then(resolved_value => {
$scope.return_message = resolved_value;
});
If you're using AngularJS 1.2 you can still use promises unwrapping but note it's deprecated and removed in version 1.3. You can find more information in the AngularJS 1.0->1.2 migration guide.
Edit (2014-10-30):
Recently AngularJS 1.3 was released. In this branch promises unwrapping is removed completely.
Change your code to
function getRandomCommit() {
var d = $q.defer();
// do async tasks
// call d.resolve(data) when finish
return d.promise;
}
getRandomCommit().then(function (data) {
$scope.return_message = data;
});
I no longer have an error in my console and bangarang can see the proper $scope.return_message thanks #miszy
function getRandomCommit()
{
var d = $q.defer();
$scope.repos = Repos.query({username: 'octocat'}).$promise.then(function ( value )
{
var ranNum = Math.floor((Math.random()*value.length) + 1);
$scope.repo = Repo.get({username: 'octocat'}, {repo: value[ranNum].name}).$promise.then(function ( value2 )
{
$scope.commits = Commit.query({username: 'octocat'}, {repo: value2.name}).$promise.then(function ( value3 )
{
var ranNum2 = Math.floor((Math.random()*value3.length));
d.resolve(value3[ranNum2].commit.message);
return d.promise;
});
});
});
}; });
});
});
return d.promise;
};
getRandomCommit().then(function( resolved_value ) {
$scope.return_message = resolved_value;
});
}]);
Related
So i'm building a web application in AngularJS that connects to an API (Qlik Sense Engine API) with QSocks.
Qsocks is a lightweight wrapper around the Qlik Sense Engine API wrapper that is written in NodeJS but can also be imported in a web environment. QSocks contains and uses the NPM package Promise package so it uses it's own non AngularJS promises.
My service looks like this:
var app_promise = (appFactory.activeConnection() ? appFactory.activeConnection() : appFactory.app());
this.getData = function(qMeasures, time) {
ratioChild.qHyperCubeDef.qMeasures[0].qDef.qDef = qMeasures;
ratioChild.qHyperCubeDef.qMeasures[0].qDef.qLabel = qMeasures;
ratioChild.qHyperCubeDef.qDimensions[4].qDef.qFieldDefs = [time];
ratioChild.qHyperCubeDef.qDimensions[4].qDef.qFieldLabels = [time];
var deferred = $q.defer();
app_promise.then(function (obj) {
obj.createChild(ratioChild).then(function (childObj) {
deferred.resolve(childObj);
});
});
return deferred.promise;
}
In simple words, when i call this service in e.g. a controller. I get an object where i can build other objects on it.
Side Note:
I do need to make a new AngularJS promise because app_promise.then and obj.createChild(ratioChild).then are the NPM promise package promises.
This is how my controller looks like (first part):
if (!$rootScope.balanceSheetFixedObj) {
var fixYearsqMeasure = "Sum({<Jaar=>}Saldo)";
balanceSheetService.getData(fixYearsqMeasure, self.time).then(function (childObj) {
$rootScope.balanceSheetFixedObj = childObj;
return childObj;
}).then(handleFixData)
} else {
handleFixData();
}
This is how my controller looks like (second part):
function handleFixData(childObj) {
childObj = (childObj) ? childObj : $rootScope.balanceSheetFixedObj;
childObj.getLayout().then(function(data) {
self.data = data;
if (data.qHyperCube.qPivotDataPages[0].qData.length > 0) {
var fixPivotData = data.qHyperCube.qPivotDataPages[0];
self.labels = fixPivotData.qLeft;
$scope.$apply(); // Here is my problem!
With $scope.$apply() my view is publishes/updated after a second.
If i leave out the $scope.$apply() it do publish/update the view but after 10-15 Seconds.. Way to late! Why is my view so slow? I would like to leave out the $scope.$apply()
I manage to solve my own problem. After looking back it was quite obvious what my problem was.
Thanks to #charlietfl i've taken a look to the childObj.getLayout(). What i saw what that the getLayout() function returns a QSocks promise and the code that updates my view was written inside of the .then() of the QSocks promise. As getLayout() is not an angular promise, this was the problem. My view was not updated properly.
My solution was to create a service function that creates an Angular Promise
this.getObjLayout = function(childObj) {
var deferred = $q.defer();
childObj.getLayout().then(function(data) {
deferred.resolve(data);
});
return deferred.promise;
}
And in the controller i invoke that function
function handleFixData(childObj) {
childObj = (childObj) ? childObj : $rootScope.balanceSheetFixedObj;
balanceSheetService.getObjLayout(childObj).then(function (data) {
self.data = data;
if (data.qHyperCube.qPivotDataPages[0].qData.length > 0) {
var fixPivotData = data.qHyperCube.qPivotDataPages[0];
self.labels = fixPivotData.qLeft;
}
})
}
Recently it has become possible to use angularjs within google apps script via the iframe sandbox mode.
My problem comes when trying to communicate with the server (gapps spreadsheet) and receiving asynchronous data in return.
The implementation for receiving data from the server is to use a function with a callback function like so:
google.script.run.withSuccessHandler(dataGatheringFunction).getServerData();
getServerData() would be a function that resides server-side that would return some data, usually from the accompanying spreadsheet. My question is how to use the callback function within the parameters of AngularJS. A typical $http function could be placed in a provider, and the scope value could be populated after then.() returns. I could also invoke $q. But how would I deal with the necessity of google's callback?
Here's a simplified version of what I'm messing with so far:
app.factory("myFactory", function($q){
function ssData(){
var TssData = function(z){
return z;
}
google.script.run.withSuccessHandler(TssData).getServerData();
var deferred = $q.defer();
var d = deferred.resolve(TssData)
console.log("DP: " + deferred.promise);
return deferred.promise;
}
return ssData();
})
Then in the controller resolve the server call similar to this:
myFactory.then(set some variables here with the return data)
My question is simply - How do I deal with that callback function in the provider?
The script throws no errors, but does not return the data from the server. I could use the old $timeout trick to retrieve the data, but there should be a better way.
You only need to $apply the output from the server function:
google.script.run.withSuccessHandler(function(data) {
$scope.$apply(function () {
$scope.data = data;
});
}).withFailureHandler(errorHandler).serverFunction();
Maybe the most elegant solution that makes sure the google.script.run callbacks are registered automatically in the AngularJS digest cycle would be to use the $q constructor to promisify the google callbacks. So, using your example above:
app.factory('myFactory', ['$q', function ($q){
return {ssData: ssData};
function ssData(){
var TssData = function(z){
return z;
};
var NoData = function(error) {
// Error Handling Here
};
return $q(function(resolve, reject) {
google.script.run
.withSuccessHandler(resolve)
.withFailureHandler(reject)
.getServerData();
}).then(TssData).catch(NoData);
}
}]);
Then in your controller you can call myFactory.ssData()
Since I don't know exactly what TssData is doing I included it here but note that this simply returns another promise in this context which you will still have to handle in your controller:
myFactory.ssData().then(function(response) {
// Set data to the scope or whatever you want
});
Alternately, you could expose TssData by adding it to the factory's functions if it is doing some kind of data transformation. If it is truly just returning the response, you could refactor the code and omit TssData and NoData and handle the promise entirely in the controller:
app.factory('myFactory', ['$q', function ($q){
return {ssData: ssData};
function ssData(){
return $q(function(resolve, reject) {
google.script.run
.withSuccessHandler(resolve)
.withFailureHandler(reject)
.getServerData();
});
}
}]);
app.controller('myController', ['myFactory', function(myFactory) {
var vm = this;
myFactory.ssData()
.then(function(response) {
vm.myData = response;
}).catch(function(error) {
// Handle Any Errors
});
}]);
An excellent article about promises (in Angular and otherwise) is here: http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
This guy seems to be pulling data from a GSheet into angular quite happily without having to do anything fancy.
function gotData(res) {
$scope.validUser = res.validUser;
var data = angular.copy(res.data), obj, i=0;
Object.keys(data).forEach(function(sh) {
obj = {title: sh, checked: {}, showFilters: false, search: {}, sort: {index: 0, reverse: false}, currentPage: 0, checkedAll: true, showBtns: true, searchAll: ''};
obj.heading = data[sh].shift();
obj.list = data[sh];
obj.heading.forEach(function(s,i) {
obj.checked[i] = true;
});
$scope.sheets.push(obj);
});
$scope.sheets.sort(function(a,b) {
return a.title > b.title ? 1 : -1;
});
$scope.gotData = true;
$scope.$apply();
}
google.script.run.withSuccessHandler(gotData).withFailureHandler($scope.gotError).getData();
My solution was to get rid of the $q, promise scenario all together. I used $rootScope.$broadcast to update scope variables from the server.
Link to spreadsheet with script.
I have a service method that has some caching logic:
model.fetchMonkeyHamById = function(id)
{
var that = this;
var deferred = $q.defer();
if( that.data.monkeyHam )
{
deferred.resolve( that.data.monkeyHam );
return deferred.promise;
} else {
return this.service.getById( id).then( function(result){
that.data.monkeyHam = result.data;
});
}
};
I know how to use $httpBackend to force the mocked data to be returned. Now, how do I force it to resolve (and then test the result) when I've set the data explicitly?
I want to test the result in the controller then() function:
MonkeyHamModel.fetchMonkeyHamById(this.monkeyHamId).then( function() {
$scope.currentMonkeyHam = MonkeyHamModel.data.monkeyHam;
});
Then my test I want to explicitly set the data (so it loads from memory "cache" instead of httpBackend)
MonkeyHamModel.data.monkeyHam = {id:'12345'};
MonkeyHamModel.fetchMonkeyHamById( '12345');
// How to "flush" the defer right here like I would have flushed httpBackend?
expect( $scope.currentMonkeyHam.id ).toEqual('12345'); //fails because it is not defined yet since the $q never resolved
Where $scope is just the scope of my controller, but called $scope here for brevity.
UPDATE:
The suggested answer does not work. I need the function to return a promise, not a value that is the result of a promise:
model._monkeyHams = {} // our cache
model.fetchMonkeyHamById = function(id){
return model.monkeyHams[id] || // get from cache or
(model.monkeyHams[id] = this.service.getById(id).then(function(result){
return result.data;
}));
};
The following requires that you have touched the server already. I create a model on the front end (currentMonkeyHam) or whatever, and don't load it back after the first POST (an unnecessary GET request). I just use the current model. So this does not work, it requires going out to the server at least once. Therefore, you can see why I created my own deferred. I want to use current model data OR get it from the server if we don't have it. I need both avenues to return a promise.
var cache = null;
function cachedRequest(){
return cache || (cache = actualRequest())
}
Your code has the deferred anti pattern which makes it complicated - especially since you're implicitly suppressing errors with it. Moreover it is problematic for caching logic since you can end up making multiple requests if several requests are made before a response is received.
You're overthinkig it - just cache the promise:
model._monkeyHams = {} // our cache
model.fetchMonkeyHamById = function(id){
return model.monkeyHams[id] || // get from cache or
(model.monkeyHams[id] = this.service.getById(id).then(function(result){
return result.data;
}));
};
In your case, you were caching all IDs as the same thing, the general pattern for caching promises is something like:
var cache = null;
function cachedRequest(){
return cache || (cache = actualRequest())
}
Creating deferred is tedious and frankly - not very fun ;)
You can use setTimeout (or $timeout) for resolving the promise.
You can modify your code as -
model.fetchMonkeyHamById = function(id)
{
var that = this;
var deferred = $q.defer();
if( that.data.monkeyHam )
{
setTimeout(function() {
deferred.resolve(that.data.monkeyHam);
}, 100);
return deferred.promise;
} else {
return this.service.getById( id).then( function(result){
that.data.monkeyHam = result.data;
});
}
};
EDIT:
Modified as per Benjamin's suggestion -
Using $rootScope.digest() - code should be something like this
MonkeyHamModel.data.monkeyHam = {id:'12345'};
MonkeyHamModel.fetchMonkeyHamById( '12345');
$rootScope.digest();
We've done something similar in our code base, but instead of having an object with state that constantly changed we went with something that looks more like a traditional repository.
someInjectedRepoistory.getMonkeyHamModel().then(x => $scope.monkeyHam = x);
Repository{
getMonkeyHamModel() {
var deferred = $q.defer();
if( this.cache.monkeyHam )
{
deferred.resolve( this.cache.monkeyHam );
} else {
return this.service.getById( id).then( function(result){
this.cache.monkeyHam = result.data;
});
}
return deferred.promise
}
}
There are no problems with returning a completed deferred. That's part of the purpose of the deferreds, it shouldn't matter when or how they are resolved, they handle all of that for you.
As for your test we do something like this.
testGetFromService(){
someInjectedRepoistory.getMonkeyHamModel().then(x => verifyMonkeyHam(x));
verifyHttpBackEndGetsCalled()
}
testGetFromCache(){
someInjectedRepoistory.getMonkeyHamModel().then(x => verifyMonkeyHam(x));
verifyHttpBackEndDoesNotGetCalled()
}
I'd like my objects to cache the result of some network requests and answer the cached value instead of doing a new request. This answer here done using angular promises looks a lot like what I'm going for, but I'm not sure how to express it using the Parse.com promise library. Here's what I'm trying...
module.factory('CustomObject', function() {
var CustomObject = Parse.Object.extend("CustomObject", {
cachedValue: null,
getValue: function() {
if (this.cachedValue) return Parse.Promise.as(this.cachedValue);
return this.functionReturningPromise().then(function (theValue) {
this.cachedValue = theValue;
return this.cachedValue;
});
},
My idea is to return a promise whether or not the value is cached. In the case where the value is cached, that promise is resolved right away. The problem is, as I follow this in the debugger, I don't seem to get the cached result on the second call.
Your value is almost correct. Your design is correct the only issue you have here is dynamic this.
In the context of the .then handler, this is set to undefined (or the window object), however - since you're using Parse promises and I'm not sure those are Promises/A+ compliant it can be arbitrary things - the HTTP request, or whatever. In strict code and a good promise library - that would have been an exception.
Instead, you can do CustomObject.cachedValue explicitly instead of using this:
var CustomObject = Parse.Object.extend("CustomObject", {
cachedValue: null,
getValue: function() {
if (CustomObject.cachedValue) return Parse.Promise.as(this.cachedValue);
return this.functionReturningPromise().then(function (theValue) {
CustomObject.cachedValue = theValue;
return this.cachedValue;
});
},
If $q promises are also possible instead of Parse promises, I'd use those instead:
var cachedValue = null;
getValue: function() {
return $q.when(cachedValue || this.functionReturningPromise()).then(function(theValue){
return cachedValue = theValue;
});
}
You can just cache the promise and return that
module.factory('CustomObject', function() {
var CustomObject = Parse.Object.extend("CustomObject", {
cachedPromise: null,
getValue: function() {
if (!this.cachedPromise) {
this.cachedPromise = this.functionReturningPromise();
}
return this.cachedPromise;
},
...
}
...
}
I am not familiar with the Parse.com promise library, but it could be a plain JS error:
The this inside the function is not referring to the Promise object, but to the global object.
Change the code like that:
...
getValue: function() {
if (this.cachedValue) return Parse.Promise.as(this.cachedValue);
var that = this;
return this.functionReturningPromise().then(function (theValue) {
that.cachedValue = theValue;
return that.cachedValue;
});
},
I've tried to strip out the details and make this fairly generalized...
Using 1.2 rc2 my code worked fine, after updating to 1.2 stable and correcting for $parse changes I've run into a binding problem. Before the update, the following code worked without any issues. updateChildObject() gets called from the html page.
.when('/the-page/', {
controller: function($scope, serviceResults, FactoryService) {
$scope.object.childObject = serviceResults;
// this function used to work. Now assigns the function to the
// scope rather than the results
$scope.updateChildObject = function(args) {
$scope.object.childObject = FactoryService.getSomethingFromServer(args);
};
},
resolve: {
serviceResults: function(FactoryService) {
return FactoryService.getSomethingFromServer(args);
}
}
Since this is failing now ($scope.object.childObject appears to get set as the function and not the results) I believe the appropriate way to solve it is through a promise. (Note, the service itself is using a promise successfully.) However, I'm having difficulty getting the $scope to update when the promise is resolved.
I believe the following code is along the right track. $q is injected in the controller.
...
$scope.updateChildObject = function(args) {
var defer = $q.defer();
defer.promise.then(function() {
return FactoryService.getSomethingFromServer(args);
});
$scope.object.childObject = defer.resolve();
};
...
So can anyone tell my what I'm doing wrong here? Promises are just one of those things that haven't really clicked for me yet.
Just as an alternative to your answer: you say FactoryService is already successfully using a promise, and in that case it seems like you don't need an additional promise in updateChildObject too. You could update FactoryService.getSomethingFromServer(args) to return a promise (i.e. with return defer.promise; at the end and defer.resolve(results); in the async bit), and then simplify updateChildObject to just:
$scope.updateChildObject = function(args) {
FactoryService.getSomethingFromServer(args).then(function(results) {
$scope.object.childObject = results;
}
};
Also, it's worth knowing that Angular 1.2 intentionally breaks automatic promise unwrapping that was in earlier versions: https://github.com/angular/angular.js/issues/4158 . It used to be the case that this code
$scope.updateChildObject = function(args) {
$scope.object.childObject = FactoryService.getSomethingFromServer(args);
};
would work identically to the one above (assuming getSomethingFromServer returns a promise), but not anymore. This might be the issue you're running into with 1.2
Figured out what I was doing wrong. Definitely was a promise issue in that I just wasn't using them correctly. The following solved it:
...
$scope.updateChildObject = function(args) {
var defer = $q.defer();
defer.promise.then(function(results) {
$scope.object.childObject = results;
});
defer.resolve(FactoryService.getSomethingFromServer(args));
};
...
So defer.resolve calls what's to be resolved. promise.then() passes the results to the next action. So simple.