Cannot read property 'then' of undefined with nested function calls - javascript

The code below is contrived. I'm simplified things as best I could to ask the question.
I have a simple angular service that makes an API call and returns results:
doWork = function(reqId) {
return $http.get('/api/dowork/' + reqId).then(function(response) {
return response.data;
}).catch(function(response) {
return $q.reject(response.data);
});
}
mediumRequest = function() {
var req = 'medium';//normally do something hard to derive this value
return this.doWork(req);
}
In my controller, I can call the doWork function on the service and get back a good response like this:
myService.doWork('simple').then(function(response){
//do something great with response
});
However, if I need to call an intermediate method to preprocess the request, I get "Cannot read property 'then' of undefined":
myService.mediumRequest().then(function(response){
//do something great with response
});
Why doesn't the function mediumRequest return the promise that doWork returned to it?

Try this code , you did wrong in your service
var app = angular.module("myApp", [])
.service('myService',function($http,$q){
this.doWork = function(reqId) {
return $http.get('/api/dowork/'+ reqId).then(function(response) {
return response.data;
}).catch(function(response) {
return $q.reject(response.data);
});
};
this.mediumRequest = function() {
var req = 'medium';//normally do something hard to derive this value
return this.doWork(req);
};
})
app.controller("myCtrl", function($scope,$compile,myService) {
myService.doWork('simple').then(function(response){
console.log('b',response)
});
myService.mediumRequest().then(function(response){
console.log('a',response)
});
})
It will work

As with most things, the problem was a coding error in my actual service code. The code I've presented in this question would work as expected. I appreciate the couple of folks who offered suggestions of ways to identify the issue. Here was my issue:
My actual "intermediate" function had this structure:
mediumRequest = function(options) {
//process first option meeting criteria
options.forEach(function (item) {
if(item.meetsCriteria)
{
var req = item.code;
return this.doWork(req);
}
});
}
As you can see, the return is actually just exiting the forEach and never actually being returned from the mediumRequest function. Hence the error.

Related

Getting empty return result in $http.get() in AngularJS 1.5.0

I have created a factory in my AngularJS application which is used to bring in refreshed data from server after a delete has been performed on one or more elements of the list.
var myApp = angular.module('app');
myApp.factory('mvListRefresher', function($http) {
return {
refreshCourses : function() {
var list = new Array();
$http.get('/api/courses').then(function(response) {
console.log('got refreshed lists');
console.log(response.data);
list = response.data;
}, function(error) {
console.log('error in retreiving fresh lists');
console.log(error);
});
console.log('Displaying list of courses');
console.log(list);
if(list.length != 0) {
return list;
}
}
}
});
I'm calling this factory in one of my controllers by calling
$scope.courses = mvListRefresher.refreshCourses();
But I'm getting an empty list, i.e I'm not getting any return value at all. In my factory function I observed that the line
console.log(list);
always prints out an empty array, however the line
console.log(response.data);
which is inside the success callback prints out the full list of objects correctly. I don't know how this is happening. I'm relatively new to AngularJS's promises and it's asynch methods and architecture. Kindly help me out. If you need any more info i'll provide it. I've done the entire app using MEAN. Thanks in advance!
$http requests are asynchronous by nature so at the point you're returning list, the request is not complete yet. You can read more about the general concepts of asynchronicity in JavaScript in this response: https://stackoverflow.com/a/14220323/704894
What you should do instead is return a Promise of list:
myApp.factory('mvListRefresher', function($http) {
return {
refreshCourses: function() {
return $http.get('/api/courses').then(function(response) {
return response.data;
});
}
};
});
$http methods return promises of results. Then you can use this function in the following way:
mvListRefresher.refreshCourses().then(function (list) {
$scope.courses = list;
});
You can read more about it in the $http documentation.

How to make http request in my case

I am trying to prevent multiple http requests being fired in my codes.
I have something like
in my controller
//if use click, fire this method
var getItems = function() {
factory.makeRequest.then(function(data){
//do stuff with data...
})
}
in my factory
var factory = {};
factory.makeRequest = function(){
if($http.pendingRequests.length===0) {
return getData()
.then(function(data) {
//do stuff here
return data;
})
}
}
factory.getData = function() {
return $http.get('/project/item');
}
return factory;
The above code will prevent the request being fired multiple time if the process is on going. However, I get Cannot read property of .then of 'undefined' if I click the button again before the promise is resolved. I know it's because the $http request doesn't return anything as I have $http.pendingRequests.length===0 condition. The above codes give me what I need but I don't know how to prevent the error in console log. Can anyone help me out? thanks a lot!
The following will return the current request promise, if it exists, otherwise it will create a new request and return that promise. It will clean up the current request on success.
var factory = {};
factory._currentRequest = null;
factory.makeRequest = function(){
if(!factory._currentRequest) {
factory._currentRequest = factory.getData()
.then(function(data) {
//do stuff here
factory._currentRequest = null;
return data;
})
}
return factory._currentRequest
}
factory.getData = function() {
return $http.get('/project/item');
}
return factory;

AngularJS $http request in a factory

I want to load a JSON file into a factory and then return its value.
Here's the code:
angular.module('getGuilds', [])
.factory('getGuilds', getGuilds);
getGuilds.$inject = ['$http'];
function getGuilds($http){
var obj = {content:null};
$http.get('guild/guilds.json').success(function(data) {
obj.content = data;
});
return obj;
}
The problem is that it only returns the object with the value of null, so it seems the $http.get doesn't change the value of the obj.content.
After this I did a little test:
$http.get('guild/guilds.json').success(function(data) {
obj.content = data;
});
console.log(obj)
return obj;
}
It gave back this object insted of the JSON's array: {content:null}.
Then I put the console.log inside the $http.get request.
$http.get('guild/guilds.json').success(function(data) {
obj.content = data;
console.log(obj)
});
Guess what, it logged out the JSON file. Would somebody be so kind as to help me?
$http.get performs the get asynchronously. The code that appears after the success block in the source runs before the code within the success block. So it's behaving just as expected: on your first attempt, you're logging the results of an unfinished request.
This is because $http.get performs the get asynchronously. So at the point you were assigning the value, it wasn't available yet. You need to return the obj inside your success function changing your code like this should make it work:
// dataservice factory
angular
.module('getGuilds', [])
.factory('dataservice', dataservice);
dataservice.$inject = ['$http'];
function dataservice($http) {
return {
getGuilds: getGuilds
};
function getGuilds() {
return $http.get('guild/guilds.json')
.then(getGuildsComplete)
.catch(getGuildsFailed);
function getGuildsComplete(response) {
return response.data;
}
function getGuildsFailed(error) {
console.log('XHR Failed for getGuilds.' + error.data);
}
}
}
In your controller you would then call the dataservice:
dataservice.getGuilds()
.then(function(data) {
vm.guilds = data;
return vm.guilds;
});
Obviously the controller requires more code to actually work. But this should give you enough info to solve your issue.

AngularJS throws exception when trying to saveChanges with breeze

I'm new to AngularJS and Breeze. I'm trying to save changes and have a problem with that. Here's my code:
In controller:
function update() {
vm.loading = true;
return datacontext.saveSettings().then(function () {
vm.loading = false; // never gets called
}).fail(function (data) {
vm.loading = false; // never gets called
});
}
In datacontext:
function saveSettings() {
if (manager.hasChanges()) {
manager.saveChanges() // breaks here if there are changes
.then(saveSucceeded)
.fail(saveFailed)
.catch(saveFailed);
} else {
log("Nothing to save");
return false;
};
}
The error is thrown in angular.js and it's very unhelpful TypeError: undefined is not a function I guess there is something simple I'm missing here, but can't figure out what is it.
Want to note that it does send correct data to SaveChanges method on server, but the error is thrown before any response from the server received. After the response is received it throws another error TypeError: Cannot read property 'map' of undefined but it might be related to the fact the response I return is invalid. I haven't got to that part yet.
Can anyone anyone help with it? I'm lost here.
UPDATE
Here is how I construct my dataservice and manager:
var serviceName = "http://admin.localhost:33333/api/breeze/"; //
var ds = new breeze.DataService({
serviceName: serviceName,
hasServerMetadata: false,
useJsonp: true,
jsonResultsAdapter: jsonResultsAdapter
});
var manager = new breeze.EntityManager({ dataService: ds });
model.initialize(manager.metadataStore);
Two problems:
Your datacontext method does not return a promise so the caller cannot find anything to hang the then or fail call on.
You should be callingcatch, not fail
1. Return a promise
Your saveSettings method did not return a result in the success case so it must fail. Your method must also return a promise in the fail case ... or it will also fail.
And while I'm here, since there is no real difference between your success and fail case, you might as well move the vm.loading toggle to the finally clause.
Try this instead:
function update() {
vm.loading = true;
return datacontext.saveSettings()
.then(function () {
// .. success handling if you have any
})
.catch(function (data) {
// .. fail handling if you have any
})
.finally(funtion() {
vm.loading = false; // turn it off regardless
});
}
And now the dataContext ... notice the two return statements return a promise.
function saveSettings() {
if (manager.hasChanges()) {
return manager.saveChanges()
.then(saveSucceeded)
.catch(saveFailed);
} else {
log("Nothing to save");
// WHY ARE YOU FAILING WHEN THERE IS NOTHING TO SAVE?
// Breeze will handle this gracefully without your help
return breeze.Q.reject(new Error('Nothing to save'));
};
}
2. Use catch
I assume you have configured Breeze to use Angular's $q for promises (you should be using the "breeze.angular" service and have injected "breeze" somewhere).
$q does not have a fail method! The equivalent is catch. For some reason you have both attached to your query. You'll get the ReferenceError exception immediately, before the server has a chance to respond ... although it will launch the request and you will get a callback from the server too.
Try just:
return manager.saveChanges()
.then(saveSucceeded)
.catch(saveFailed);
You see many Breeze examples that call fail and fin. These are "Q.js" methods; "Q.js" is an alternative promise library - one used by Breeze/KnockOut apps and it was the basis for Angular's $q.
Both "Q.js" and $q support the now-standard catch and finally promise methods. We're slowly migrating our example code to this standard. There is a lot of old fail/finally code around in different venues. It will take time.
Sorry for the confusion.
Update savesetting function like below to return Promise.
function saveSettings() {
if (manager.hasChanges()) {
return manager.saveChanges(); // return promise
} else {
log("Nothing to save");
return false;
};
}
Then you can call then and fail in update function like following.
function update() {
vm.loading = true;
return datacontext.saveSettings().then(function () {
vm.loading = false; // never gets called
}).fail(function (data) {
vm.loading = false; // never gets called
});
}

AngularJS : Restangular Library

I am experimenting with the fabulous Restangular library, and I have run into some trouble when trying to return some JSON objects and bind them to the $scope object.
I have this function inside my Factory script, which essentially is just returning a list of blogs utilising the Restangular.getList() function.
angular.module("ecmaworld").factory("Authenticate", function(Restangular) {
// Testing Function
var Blogs = Restangular.all("blogs");
return {
blogs: function() {
Blogs.getList("blogs").then(function(blogs) {
return blogs;
}, function(error) {
console.log(error);
});
}
};});
The Factory method here returns the a list of blog posts. I then attempt to call this function which I ahve defined in the Factory script and utilise it in one of my controller scripts like this:
angular.module("ecmaworld").controller("LoginController", function($scope, Authenticate) {
//Testing function
$scope.getBlogs = function() {
$scope.blogs = Authenticate.blogs();
};
});
When I attempt to use the "data-ng-repeat" directive to loop through all the blogs posts, I do not get anything displaying. I can see that teh API method has worked through google developers as the http response returns the JSON objects. However, I am unsure to why they are not displaying when attempting to loop through them in the VIEW. If you could please assist me, that would be much appreciated. I am only new to Angular, so go easy guys.
Thanks.
The blogs() method needs to return the promise...
blogs: function() {
return Blogs.getList("blogs").then(function(blogs) {
return blogs;
}, function(error) {
console.log(error);
});
}
And then assign the result to the scope property when the promise is resolved...
$scope.getBlogs = function() {
Authenticate.blogs().then(function (blogs) {
$scope.blogs = blogs;
});
};

Categories