I use the strophe.archive plugin but the done function of the Deferred object seems to be "undefined" after Strophe sends the query. I also added the plugin to the "shim" of require.js:
var q = $.Deferred();
q = connection.archive.listCollections("user#server.lit");
console.log(q);
q.done(function(){
console.log("DONE");
});
I get the following trace:
Uncaught TypeError: Cannot call method 'done' of undefined test.js:23
Backbone.View.extend.initialize test.js:23
Backbone.View backbone.js:1148
child backbone.js:1393
Backbone.Router.extend.pageTest router.js:92
(anonymous function) backbone.js:900
(anonymous function) backbone.js:1082
_.some._.any underscore.js:193
_.extend.loadUrl backbone.js:1080
_.extend.navigate backbone.js:1125
_.extend.navigate backbone.js:909
Backbone.View.extend.pageTest test1.js:199
jQuery.event.dispatch require-jquery.js:5385
elemData.handle.eventHandle
shim:
archive: {
deps: ["jquery", "strophe"],
exports: "archive"
},
You invoke a deferred obj and assign it to q, and then you assign the result of listCollections to q. These are two different and completely unrelated assignments. Just because you made q a deferred initially doesn't mean that the result from listCollections is now somehow deferrable.
var q = $.Deferred(); // q is a deferred obj
q = connection.archive.listCollections("user#server.lit"); // q is undefined
listCollections must return a deferred object for you to be able to call done on it.
Looking at the plugin code, it looks like listCollections doesn't return anything.
Instead, you must pass listCollections a callback function:
var q = $.Deferred();
q = connection.archive.listCollections("user#server.lit", null, function(){
console.log("DONE");
});
Alternatively, you can make a wrapper function for listCollections that will return a deferred.
function deferrableListCollections () {
var q = $.Deferred();
connection.archive.listCollections("user#server.lit", null, function(){
q.resolve() ;
});
return q;
}
No you can call something similar to your original code:
q = deferrableListCollections("user#server.lit");
q.done(function(){
console.log("DONE");
});
Related
I'm trying to wrap a third party library to return an object that resolves into an object that can be displayed in the view, similar to how $resource() works. I'm aware that I can manually do .then() on the promise and then set the value, but I wanted the result to seamlessly return similar to how I can do:
this.Value = $resource("/someresource").get();
How would I change the below SomeThirdPartyFunction() to return an object that resolves in the view.
Here's an example of what I'm trying to do:
angular.module('testApp', []).controller('TestController', function ($timeout, $q) {
var TestController = this;
var SomeThirdPartyFunction = function () {
var Deferred = $q.defer();
var Promise = Deferred.promise;
$timeout(function () {
Deferred.resolve("abcd");
}, 3000);
return Promise;
};
TestController.Value = SomeThirdPartyFunction();
/* I don't want to do this:
SomeThirdPartyFunction().then(function(Value) {
TestController.Value = Value;
});*/
});
And here's a plunker: https://plnkr.co/edit/HypQMkaqXmFZkvYZXFXf?p=preview
Every example I've seen using promises just wraps $http calls, but I haven't seen any examples of calling third party libraries that return promises that resolve into objects.
From the AngularJS document:
It is important to realize that invoking a $resource object method
immediately returns an empty reference (object or array depending on
isArray). Once the data is returned from the server the existing
reference is populated with the actual data.
So instead of return a promise, you can do something like this:
var SomeThirdPartyFunction = function() {
var getAComplextObject = function() {
return {
number: 42,
method: function() {
return this.number + 1;
}
};
};
var returnValue = {};
$timeout(function() {
console.log("Resolved!");
Object.assign(returnValue, getAComplextObject());
}, 1000);
return returnValue;
};
You can wrap it in a promise and make the promise part of the return value, doing that you can make it thenable (aka a Promise)
Under-the-hood, $resource uses angular.copy:
function myResourceGet(params) {
var emptyObj = {};
emptyObj.$resolved = false;
emptyObj.$promise = $http.get(url, {params:params})
.then(function(response) {
angular.copy(response.data, emptyObj);
}).finally(function() {
emptyObj.$resolved = true;
});
return emptyObj;
}
From the Docs:
angular.copy Overview
Creates a deep copy of source, which should be an object or an array.
If a destination is provided, all of its elements (for arrays) or properties (for objects) are deleted and then all elements/properties from the source are copied to it.
The $resource service only works when the data is an object or an array. It does not work with primitives such as a string or number.
I have an array of links, which I get in first request. My goal is to go to every link to gather data. So I want to make a promise for every request, push them all into an array and then pass to Q.all to resolve all the promises. The problem is I can't return promise and go to the next link
Here is the function, where I tried to make multiple requests and gather data
function arrayPromise(linksArr){
function collectingData(elem){
var deferredNew = Q.defer();
var url = elem;
request(url, function(error,response,html){
if(error){
deferredNew.reject(error);
}
var $ = cheerio.load(html);
var title, content;
$('.entry-title').filter(function(){
var data = $(this);
var title = data.text();
items.text.push(
{ titleof: title }
)
})
$('.entry-content ').filter(function(){
var data = $(this);
var content = data.html();
items.text.push(
{ contentof: content})
})
deferredNew.resolve(items);
})
console.log("Returning the promise");
return defferedNew.promise;
}
var promiseArr;
console.log("LENGTH:");
console.log(linksArr.length);
for (var i = 0; i < linksArr.length; i++) {
console.log(linksArr[i]);
var tempPromise = collectingData(linksArr[i]);
console.log(tempPromise);
promiseArr.push(tempPromise);
};
return promiseArr;
}
And how I try to use it
var linksPromise = fetchLinks();
linksPromise.then(function(arr){
console.log("LINKS PROMISE RESOLVED");
Q.all(arrayPromise(arr)).then(function(data){
console.log("SUCCESS RESOLVING ALL PROMISES")
console.log(data);
},function(err){
console.log("ERROR RESOLVING ALL PROMISES", err);
});
},function(err){
console.log(err);
})
promiseArr should be declared as an array:
var promiseArr = [];
If that doesn't fix it, please provide the error that you might be seeing.
There are MULTIPLE problems
First is in
deferredNew.resolve(items);
items is defined in a local scopes not defined anywhere in scope where deferredNew.resolve(items); evaluated.
Another: Assigning empty array to promiseArr would help too.
One more: request(url, function(error,response,html) is not assigning result anywhere and your function has no return statement where you think you return promice deferredNew.resolve(items);
PS
There are more erros, check that all your function return value, for example $('..').filter(...) does not reurn values
I am writing tests for a controller. One method calls a method in a service, which utilises a promise. In my test, I have mocked the service, and (I think) correctly mocked the promise. I have been following this blog entry: http://codingsmackdown.tv/blog/2012/12/28/mocking-promises-in-unit-tests/.
Here is the test code:
describe('Controller: ResultsController', function () {
'use strict';
var ctrl;
var ResultsServiceMock;
var RouteServiceMock;
var $scope;
var mockResults;
var mockPromise;
var q;
var deferred;
beforeEach(module('waApp'));
beforeEach(function() {
ResultsServiceMock = {
get: function(query) {
deferred = q.defer();
return deferred.promise;
}
};
RouteServiceMock = {
getParams: function() {
}
};
});
beforeEach(inject(function(
$rootScope,
$controller,
$q
) {
$scope = $rootScope.$new();
q = $q;
ctrl = $controller('ResultsController', {
$scope: $scope,
results: ResultsServiceMock,
route: RouteServiceMock
});
}));
it('Should simulate requesting results from the api', function() {
spyOn(ResultsServiceMock, 'get').andCallThrough();
spyOn(RouteServiceMock, 'getParams').andReturn({input:'hamburger'});
$scope.getResults({input:'hamburger'}); // TODO give params. try query()
deferred.resolve();
$scope.$root.$digest();
expect($scope.getResults).toHaveBeenCalled();
});
});
However, when I run the tests, I get the following error:
Chrome 35.0 (Mac) Controller: ResultsController Should simulate requesting results from the api FAILED
TypeError: Cannot read property 'resolve' of undefined
at null.<anonymous> (/Users/maryc/wa/app/results/results-controller_test.js:70:17)
Chrome 35.0 (Mac): Executed 14 of 14 (1 FAILED) (0.313 secs / 0.093 secs)
I don't understand where this error is coming from; is it because the spy call on ResultsServiceMock is not working? Any help would be appreciated.
The function getResults is as follows:
$scope.getResults = function(params) {¬
$scope.$emit('startGetResults');¬
$scope.loading = true;¬
$scope.podRequestStatus.init = false;¬
$scope.podRequestStatus = {¬
async: {}¬
};¬
var didyoumeans;¬
if(params.didyoumeans) {¬
didyoumeans = params.didyoumeans;¬
delete params.didyoumeans;¬
}¬
ResultsService.get(params).success(function(result) {¬
$scope.$emit('getResultsSuccess');¬
if(!_.isUndefined(didyoumeans)) {¬
$scope.results.queryresult.didyoumeans = didyoumeans;¬
} else if(!_.isUndefined(result.queryresult.didyoumeans)) {¬
if (!_.isArray(result.queryresult.didyoumeans)){¬
result.queryresult.didyoumeans = [result.queryresult.didyoumeans];¬
}¬
$scope.getResults({input: result.queryresult.didyoumeans[0].val, didyoumeans: result.queryresul t.didyoumeans});¬
return;¬
}¬
$scope.loading = false;¬
$scope.podRequestStatus.init = true;¬
if(result.queryresult.success === false) { //TODO is this result.results.queryresult.success??¬
if(result.queryresult.error !== false) {¬
$log.error('Results error', 'code: ' + result.queryresult.error.code, 'msg: ' + result.quer yresult.error.msg);¬
switch (result.queryresult.error.code){¬
case '3000':¬
$location.url('/blockedip');¬
break;¬
}¬
return;¬
}¬
if($scope.results.queryresult.examplepage && $scope.results.queryresult.examplepage.category) { ¬
$scope.examples();¬
}¬
// convert tips to an array if we have a single item¬
if($scope.results.queryresult.tips && !_.isArray($scope.results.queryresult.tips)){¬
$scope.results.queryresult.tips = [$scope.results.queryresult.tips];¬
}¬
$log.error('Results error');¬
return;¬
}¬
$scope.results.queryresult.pods = _.map($scope.results.queryresult.pods, function(pod) {¬
pod.priority = PodService.priority.initial;¬
return pod;¬
});¬
if ($scope.results.queryresult.sources && _.where($scope.results.queryresult.sources, {'text':'Fina ncial data'})) {¬
$scope.$emit('financialData', true);¬
} else {¬
$scope.$emit('financialData', false);¬
}¬ ¬
$scope.asyncPods(PodService.priority.async, 'async');¬
$scope.recalculate();¬
$scope.related();¬
}).error(function() {¬
$log.error('error occurred during ResultsService.get call in ResultsController');¬
});¬
};¬
The functions asyncPods, recalculate and related are three other methods within the ResultsController.
Edited: Having fixed the first error, I now get the following error when running the tests:
Chrome 35.0 (Mac) Controller: ResultsController Should simulate requesting results from the api FAILED
TypeError: undefined is not a function
at Scope.$scope.getResults (/Users/maryc/wa/.tmp_test/results-controller.js:222:36)
at null.<anonymous> (/Users/maryc/wa/app/results/results-controller_test.js:67:16)
This error comes from the line at the beginning of getResults() which calls ResultsService.get(). This seems to imply that my promise is either not being resolved, or the call $scope.getResults() is somehow failing?
The code for the .get() function of ResultsService is:
get: function(query) {¬
this.reset();¬
return ApiService.get({¬
params: UtilService.merge(query, {¬
async: true,¬
scantimeout: 1,¬
formattimeout: 8,¬
parsetimeout: 5,¬
format: 'image,plaintext,imagemap',¬
banners: 'true'¬
}),¬
timeout: abort.promise,¬
type: 'init',¬
cache: UserService.user.cacheResults¬
}).success(function(data){results.queryresult = data.queryresult;});¬
},¬
I'm wondering now if the problem is that .get itself contains a promise?
From the information you have included so far, this is my guess of what might cause the problem.
In your $scope.getResults() method, you call the get() method of ResultsService, so it seems like a name of the service is ResultsService.
But in the unit testing, you pass the ResultsServiceMock as results:
ctrl = $controller('ResultsController', {
$scope: $scope,
results: ResultsServiceMock,
route: RouteServiceMock
});
It should be ResultsService instead like this:
ctrl = $controller('ResultsController', {
$scope: $scope,
ResultsService: ResultsServiceMock,
RouteService: RouteServiceMock
});
After this change, you might encounter another problems, but it should take you pass the error:
TypeError: Cannot read property 'resolve' of undefined
Hope this helps.
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 have an AngularJS service that contains the function getServiceData:
var DataService = (function () {
function DataService($log, $http, config) {
this.$log = $log;
this.$http = $http;
this.config = config;
}
DataService.prototype.getServiceData = function (fullUrl, qsData, rootNode) {
var _this = this;
if (qsData === null || typeof qsData === "undefined") {
qsData = null;
}
return this.$http.jsonp(fullUrl, { params: qsData }).then(function (response) {
var data = rootNode === null ? response.data : eval("response.data." + rootNode);
return data;
});
};
return DataService;
})();
JSON data is returned, but if I pass a rootNode argument, in the "then" code it is always null. Any idea on how I can access the rootNode argument within the "then" code?
$http return promise, from AngularJs Promise Api
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.
then function take three callback function not any valued parameter and it is called after your ajax request completed. So if you want to access any variable or object within then, you need to assign outer scope of then what you already did in comment.
You should use angular eval method or #Khanh TO mentioned way instead of JavaScript eval.
The scope of eval is unreliable across browsers, that's the problem.
JavaScript Closure - Eval() and capturing variables in Eval()'s scope
Using eval is not recommended. In your case, you should use bracket notation syntax instead:
var data = rootNode === null ? response.data : response.data[rootNode];