I have created a factory, to handle all my http related calls. It returns following inline code:
return {
get: function (opts) {
var deferred = $q.defer();
var def = defaultOptions(HttpMethod.Get);
$.extend(def, opts);
$http({ method: 'get', url: config.remoteServiceName + def.url }).then(function (result, status, header) {
def.success(deferred, { data: result });
}, function (data) {
def.error(deferred, data);
});
return deferred.promise;
},
post: function (opts) {
var deferred = $q.defer();
var def = defaultOptions(HttpMethod.Post);
$.extend(def, opts);
$http.post(config.remoteServiceName + def.url, def.data).then(function (result) {
def.success(deferred, result);
}, function (data) {
def.error(deferred, data);
});
return deferred.promise;
},
remove: function (opts) {
var deferred = $q.defer();
var def = defaultOptions(HttpMethod.Delete);
$.extend(def, opts);
$http({ method: 'delete', url: config.remoteServiceName + def.url }).then(function (result) {
def.success(deferred, result);
}, function (data) {
def.error(deferred, data);
});
return deferred.promise;
}
};
Now, when i am making the calls, if there are few parallel calls being made, all promise resolution is getting mixed up. I am getting the resultset from one request in another's resolution.
Not able to solve the problem. What am i doing wrong?
Issue was not of parallel calls. It was somewhere else. JS model for ajax execution works fine, and no issue with $http either. We were using WebAPI as backend, and some of our global filters had state, which got altered on each call. Hence the data was mixed up. No issue on client end.
Thanks everyone
Related
I am working on a chat app which is Node.js + MongoDB (Mongoose library) on the server side, and Angular.js on the client side.
I have a database collection (MongoDB) for rooms (all the rooms in the app), which looks like this:
// ------- creating active_rooms model -------
var active_rooms_schema = mongoose.Schema({
room_name: String,
users: [String]
});
var active_rooms = mongoose.model('active_rooms', active_rooms_schema);
This database contains a room with all its users (i.e. "my cool room" with users: "mike", "joe", and "dave").
What I want to do is - every time a user wants to be in a chat room (with some room name) in my Angular.js client, I want to:
Create the room if it is not exists
Push that user into the users array of the room.
I know that because of 1, I will always have a room with an array of users.
This is my Angular relevant code: (I cannot give here the whole app because it is way too large and not relevant.)
$scope.enterRoom = function(info) {
$q.when(create_room_if_not_exists($scope.room)).then(add_user_to_room($scope.name, $scope.room));
$location.path("chat");
}
var create_room_if_not_exists = function(room_name) {
var deferred = $q.defer();
is_room_already_exists({
'name': room_name
}).then(function(response) {
if (!response.data.is_room_exists) {
register_room({
'name': room_name
});
console.log("room: " + room_name + ", was created");
deferred.resolve();
}
}, function(error) {
deferred.reject(error.data);
});
return deferred.promise;
}
var add_user_to_room = function(user_name, room_name) {
console.log(user_name)
add_user_to_room_request({
'user_name': user_name,
'room_name': room_name
});
}
var is_room_already_exists = function(info) {
return $http({
url: '/is_room_already_exists',
method: 'POST',
data: info
});
}
var add_user_to_room_request = function(info) {
$http({
url: '/add_user_to_room',
method: 'POST',
data: info
});
}
var register_room = function(info) {
return $http({
url: '/register_room',
method: 'POST',
data: info
});
}
What happens is that the 2nd action happens before the 1st one. When I print a log into the console, I see that, and I don't know why.
Both of these actions arrive through an HTTP request to the server - so I don't think the problem is there.
I am talking about the XHRs not chaining
A common cause of problems with chaining is failure to return promises to the chain:
var add_user_to_room = function(user_name, room_name) {
console.log(user_name)
//add_user_to_room_request({
return add_user_to_room_request({
//^^^^^^ ----- be sure to return returned promise
'user_name': user_name,
'room_name': room_name
});
}
If chaining is done properly, $q.defer is not necessary:
var create_room_if_not_exists = function(room_name) {
//var deferred = $q.defer();
//is_room_already_exists({
return is_room_already_exists({
//^^^^^^ --- be sure to return derived promise
'name': room_name
}).then(function(response) {
if (!response.data.is_room_exists) {
console.log("room: " + room_name + ", to be created");
//register_room({
return register_room({
//^^^^^^ ----- return promise to further chain
'name': room_name
});
//deferred.resolve();
} else {
return room_name;
//^^^^^^ ----- return to chain data
};
}, function(error) {
//deferred.reject(error.data);
throw error;
//^^^^^ ------- throw to chain rejection
});
//return deferred.promise;
}
If a promise is returned properly, $q.when is not necessary:
$scope.enterRoom = function(info) {
//$q.when(create_room_if_not_exists($scope.room))
// .then(add_user_to_room($scope.name, $scope.room));
return create_room_if_not_exists($scope.room)
.then(function() {
return add_user_to_room($scope.name, $scope.room));
}).then(function()
$location.path("chat")
});
}
The rule of thumb with functional programming is -- always return something.
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.
-- AngularJS $q Service API Reference - Chaining Promises.
Try this:
$scope.enterRoom = function (info) {
return create_room_if_not_exists($scope.room)).then(function(){
return add_user_to_room_request({ 'user_name': $scope.name, 'room_name': $scope.name });
}).then(function(){
$location.path('chat');
});
}
var create_room_if_not_exists = function (room_name) {
var deferred = $q.defer();
return is_room_already_exists({ 'name': room_name }).then(function (response) {
if (!response.data.is_room_exists) {
console.log("room: " + room_name + ", is being created");
return register_room({ 'name': room_name });
}
return response;
})
}
var is_room_already_exists = function (info) {
return $http({
url: '/is_room_already_exists',
method: 'POST',
data: info
});
}
var add_user_to_room_request = function (info) {
return $http({
url: '/add_user_to_room',
method: 'POST',
data: info
});
}
var register_room = function (info) {
return $http({
url: '/register_room',
method: 'POST',
data: info
});
}
I am trying to set up a service that, when I feel like it, I can flip to live data coming from an API. The getData function takes skip/take parameters to define start record and number of records.
My data is currently in a json file:
{
"Data":[{
"Id":"1462",
"Name":"Cynthia"
},{
"Id":"1463",
"Name":"Bob"
},{
...
}],
"Total":71
}
My service currently pulls all json data at once:
var _getData = function (optionsData) {
return $http({
method: 'GET',
url: 'data/packages.json'
})
.then(function successCallback(response) {
return response;
},
function errorCallback(response) {
return response;
});
}
It seems to me that I have to write the paging logic right into the service:
.then(function successCallback(response) {
var records = response.data.Data;
var firstRecord = 0;//optionsData.skip;
var numberOfRecords = 1;//optionsData.take;
response.data.Data = records.slice(firstRecord, firstRecord + numberOfRecords);
return response;
},
Is this the right way in principle?
[ UPDATE ] The controller method:
var getPackageData = function (options){
return dataService.getData(options.data).then(
function successCallback(response) {
options.success(response.data.Data);
$scope.totalCount = response.data.Total;
},
function errorCallback(response) {
// handle error
}
);
};
my errorCallback is wrong? How?
The errorCallback is converting a rejected promise to a fulfilled promise.
To avoid conversion, either throw the response or return $q.reject:
var _getData = function (optionsData) {
return $http({
method: 'GET',
url: 'data/packages.json'
})
.then(function successCallback(response) {
return response;
},
function errorCallback(response) {
//To avoid converting reject to success
//DON'T
//return response;
//INSTEAD
throw response;
//OR
//return $q.reject(response);
});
}
Another common error is to fail to include either a throw or return statement. When a function omits a return statement, the function returns a value of undefined. In that case, the $q service will convert rejected promises to fulfilled promises which yield a value of undefined.
Controller.js:
service.getUsers(params).$promise.then(function(response) {
var data = response;
$scope.users = data.resources;
$scope.totalItems = data.totalResults;
});
Service.js:
return $resource('/Users', {}, {
getUsers: {
method: 'GET',
headers: {'segment' : 'computedSegment'},
isArray: true,
transformResponse: function(data, header){
var wrapped = angular.fromJson(data);
angular.forEach(wrapped.resources, function(item, idx) {
wrapped.resources[idx] = item;
});
var deffered = $q.defer();
deffered.resolve(wrapped);
return deffered.promise;
}
}});
The issue i am getting, is that although the request goes to the server, i receive a response, the data is correctly processed, the ,,then" function will never be executed to set the $scope variables.
Does anyone have an idea?
I guess you forgot to use query() method after getUsers method. And I think you don't need to use promise in transformResponse method. You can check this answers.
I'm trying to run a sequence (one at at time, not in parallel) of promises for http GETs. Think of submitting a cooking recipe and the server will only accept one step per HTTP request. And they have to be sent in order or the pie will be a mess. And the last step is the only one returning data (a hot apple pie).
I have created a Plunker http://plnkr.co/edit/yOJhhw?p=preview
We'll start with the controller
angular.module("app")
.controller("sequencialCtrl", ["$scope", "seq", function($scope, seq) {
The controller starts with a list of steps represented by library files so they will take >0 time to d/l.
var recipeSteps = [
'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js',
'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js',
'https://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/angular-material.min.js'
];
Then I create a inters scope variable to display the first 80 chars of the retrieved js files for testing purposes only.
$scope.inters = seq.intermediates;
Then I try to call the factory provided promise. This crashes in the console at the then
// the console shows that seq.submitRecipe is undefined
seq.submitRecipe.then(function(data) {
$scope.results = data;
});
}]);
Now the Factory
angular.module('app').factory('seq', function($http, $q) {
and we create intermediates and submitRecipe
var intermediates = ['test1', 'test2'];
var submitRecipe = function(theSteps) {
var deferredRecipe = $q.defer();
var deferredPromise = deferredRecipe.promise;
deferredPromise
.then(function() {
var deferred = $q.defer();
var promise = deferred.promise;
$http({
method: 'GET',
url: theSteps.shift()
}).then(function(response) {
intermediates.push( response.data.substr(0, 80) );
deferred.resolve(response.data.substr(0, 80));
});
return promise;
})
.then(function() {
var deferred = $q.defer();
var promise = deferred.promise;
$http({
method: 'GET',
url: theSteps.shift()
}).then(function(response) {
intermediates.push( response.data.substr(0, 80) );
deferred.resolve(response.data.substr(0, 80));
});
return promise;
})
.then(function() {
var deferred = $q.defer();
var promise = deferred.promise;
$http({
method: 'GET',
url: theSteps.shift()
}).then(function(response) {
intermediates.push( response.data.substr(0, 80) );
deferred.resolve( "Apple Pie");
});
return promise;
});
As noted before I only want to return the data from that last then which is "Apple Pie".
We close out the submitRecipefunction with...
// if this resolve isnt here, nothing is execute
deferredRecipe.resolve();
$rootScope.$apply();
return deferredPromise;
};
I have found that if I dont have that resolve() the thens aren't run.
And finally we expose our factory's methods.
return {
submitRecipe: submitRecipe,
intermediates: intermediates
};
});
At the end of the day I would like $scope.results to be "Apple Pie".
Appreciate any help.
Here is the working plunkr
There were a couple of edits which had to be made:
submitRecipe is a function, so you call it in this way:
seq.submitRecipe(recipeSteps).then(function(data) {
$scope.results = data;
});
Then you may remove the unnecessary $rootScope.$apply():
deferredRecipe.resolve();
// $rootScope.$apply();
return deferredPromise;
I have a method seatClicked() that calls getUserID() to get the user id corresponding to the session atribute 'user'. There is a table that contains the username and user id(unique). The following is the definition of seatClicked()
$scope.seatClicked = function() {
promise = $scope.getUserID();
promise.then(function(results){
$scope.seatID.userID = results; // store the user id in $scope.seatID.userID
});
}
This is the definition for getUserID()
$scope.getUserID = function() {
var deferred = $q.defer();
$http({
method : 'POST',
url : 'http://localhost:8080/AirlineApp/getUserID',
headers : {
'Content-Type' : 'application/json'
}
}).then(function(data){
alert("userID:"+data)
deferred.resolve(data);
})
return deferred.promise;
};
The variable 'results' returned by $http.then() is always undefined, whereas if I use $http.success() I am able to retrieve the user id.
I want to fetch the user id before further processing. Is there any way for the function to wait till the data is fetched from the database?
P.S. I tried callback as well, no luck.
Edit: I am able to fetch the id and store it (thanks all), but it takes much longer than the time taken to execute my next operation(not presented here). Can I stall that operation till I am assured of the id?
what we did in our project, added PromiseUtils service for any REST calls
.service("PromiseUtils", function($q) {
return {
getPromiseHttpResult: function (httpPromise) {
var deferred = $q.defer();
httpPromise.success(function (data) {
deferred.resolve(data);
}).error(function () {
deferred.reject(arguments);
});
return deferred.promise;
}
}
})
and use it nice and easy
var anyCall = $http({
method: 'POST',
url: 'http://localhost:8080/AirlineApp/getUserID',
headers: {
'Content-Type': 'application/json'
}
});
PromiseUtils.getPromiseHttpResult(anyCall).then(function(result){
console.log("result", result);
})
or
PromiseUtils.getPromiseHttpResult($http.get('/api/get/call'))
.then(function(result){
console.log("result", result);
})
PromiseUtils.getPromiseHttpResult($http.post('/api/post/call', data))
.then(function(result){
console.log("result", result);
})
if you need error() function, just add as second parameter
PromiseUtils.getPromiseHttpResult($http.get('/api/get/call'))
.then(function(result){
console.log("result", result);
}, function (arguments) {
console.log("fail", arguments);
})
$http returns a special promise different from the standard promise like here https://github.com/promises-aplus/promises-spec
If you use $http you need to retrieve your data like this :
$http
.success(success)
.error(error) ...
But you can use standard promise by wrapping the $http promise in your service using $q like this :
var defer = $q.defer();
var http = $http({
url: SharedFactory.endPoint + '/stats',
method: 'GET',
params: params
});
http.success(function(stats) {
defer.resolve(stats);
})
.error(function() {
defer.reject("Failed to get stats");
});
return defer.promise;