I need to call the function after 2 request is done. So I try to use $q.all for this. I have service on Angular it calls submitForm, before submit I need to do 2 another request, and after they response - make post request to submit form. It looks like this:
submitForm : function(form, params) {
var self = this;
var callback = function () {
// this is submit post req
};
angular.forEach(form, function(item) {
// I search tables (type of field) and then send req for saving tables on backend
self.uploadTables(item)
})
this.uploadFiles(params).then(callback)
}
uploadFiles : function(params) {
// this func upload files before submit
}
uploadTables : function(params) {
// for uploading tables I have another service on backend, so I need another request, before submit
}
In $q.all I guess I need to call it like $q.all(this.uploadFiles, this.uploadTables)? But I cant do that couse uploadTables I call in forEach. How can I call callback function after complete uploadFiles and uploadTables?
var promises = [];
angular.forEach(form, function(item) {
promises.push(self.uploadTables(item).then(uploadTableCallback));
})
promises(this.uploadFiles(params).then(uploadFilesCallback));
$q.all(promises).then(allCallback)
I want also to notice that there is nothing special in $q.all and you can always do it manually:
var promisesToResolve = arr.length;
var promisesResolve = 0;
angular.forEach(arr, function(obj) {
$http.post(obj).then(function() {
promisesResolve++;
if (promisesResolve == promisesToResolve) {
// all resolved
}
})
})
You can return and collect promises in array
var promises = [];
angular.forEach(form, function(item) {
var deferred = $q.defer();
//do your code
deferred.resolve(result);
promises.push(deferred.promise);
});
// execute all the promises and do something with the results
$q.all(promises).then(
function(results) {
//do your stuff
},
// error
function(response) {
}
);
Related
I have made 2 promises in my service. The first one connects to the websocket
connect : function() {
var deferred = $q.defer();
var promise = deferred.promise;
this.socket = new WebSocket('ws://localhost');
this.socket.addEventListener('open', function (e) {
deferred.resolve("connected")
});
return promise;
}
and the second one sends a message over the websocket
send: function (msg, scope) {
var deferred = $q.defer();
var promise = deferred.promise;
this.socket.send(angular.toJson(msg))
return promise
}
In my controller i want to send only when the connection is open. To accomplish that my code looks like this
myService.connect()
.then(function() {
var myData = myService.send(data);
}
myData.then(function(d) {
// do something with the data
});
It works this way, but why do i have the feeling that this is not the way to be done? I'm pretty new to angularJS and Promises. I feels like creating a new promise inside of a .then of another promise is dirty coding.
Found this great explanation that does the exact same thing, but still i'm not convinced. If i want to send 20 messages over the websocket, it's like i physically work inside that one myService.connect promise the whole controller.
myService.connect()
.then(function() {
var myData = myService.send(data);
}
.then(function() {
var myData = myService.send(data1);
}
.then(function() {
var myData = myService.send(data2);
}
.then(function() {
var myData = myService.send(data3);
}
I have task groups, these groups have tasks. You can add existing tasks to your group, but also make new ones. These new ones don't have an _id yet in my mongoDB, so I have to make them first, before making my createTaskGroup call.
When I call createTaskGroup, I loop through the tasks, when there is no _id, I call "addnewtask". The problem is, that the last function "apiFactory.createTaskGroup" is called before the loop for making non existing tasks is done.
How can I wait for these functions to finish before executing createTaskGroup?
dvm.createTaskGroup = function (){
for (var i = 0; i < dvm.taskgroup.tasks.length; i++) {
if (angular.isUndefined(dvm.taskgroup.tasks[i]._id)) {
apiFactory.addNewTask(dvm.taskgroup.tasks[i].description, function (response) {
dvm.taskgroup.tasks[i] = response;
});
}
}
apiFactory.createTaskGroup(dvm.taskgroup, function (response) {
$mdDialog.hide(dvm.taskgroup);
})
};
I also tried using promises, normally I use callbacks, but I read about $q.all. So I would give it a shot. But then I can the complain about cors even it's the same call as before but with the use of promise.
dvm.createTaskGroup = function (){
var callsToWaitForBeforeContinue = [];
var tempArrayWithTasksWithId = [];
angular.forEach(dvm.taskgroup.tasks, function(task){
if(angular.isUndefined(task._id)){
callsToWaitForBeforeContinue.push(apiFactory.addNewTaskWithPromise(task.description));
}
else{
tempArrayWithTasksWithId.push(task);
}
});
$q.all(callsToWaitForBeforeContinue).then(function(req){
dvm.taskgroup.tasks = tempArrayWithTasksWithId;
angular.forEach(req, function(singlePromise){
dvm.taskgroup.tasks.push(singlePromise);
});
});
apiFactory.createTaskGroup(dvm.taskgroup, function (response) {
$mdDialog.hide(dvm.taskgroup);
});
};
Here is the http post itself.
var addNewTaskWithPromise = function(taskDescription){
var q = $q.defer();
$http.post(ENV.api + 'tasks/', taskDescription).then(function(response){
q.resolve(response);
}, errorCallback);
return q.promise;
};
You should be able to just call like so:
apiFactory.addNewTaskWithPromise(task.description).then(function(response){
dvm.taskgroup.tasks[i] = response;
apiFactory.createTaskGroup(dvm.taskgroup).then(function (response2) {
$mdDialog.hide(dvm.taskgroup);
});
});
got it to work. I return my http call as a promise, instead of making a variable for it
var addNewTaskWithPromise = function(taskDescription) {
return $http.post(ENV.api + 'tasks', {
"description": taskDescription
});
};
Call the function "createtaskgroup" in the "then" statement of my $q.all. Can't really explain the details why it works now, without the temp variable for my promise, I didn't receive a CORS error, probably someone here that could explain why.
dvm.createTaskGroup = function() {
var callsToWaitForBeforeContinue = [];
var tempArrayWithTasksWithId = [];
angular.forEach(dvm.taskgroup.tasks, function(task) {
if (angular.isUndefined(task._id)) {
callsToWaitForBeforeContinue.push(apiFactory.addNewTaskWithPromise(task.description));
} else if(angular.isDefined(task._id)) {
tempArrayWithTasksWithId.push(task);
}
});
$q.all(callsToWaitForBeforeContinue).then(function(req) {
dvm.taskgroup.tasks = tempArrayWithTasksWithId;
angular.forEach(req, function(singlePromise) {
dvm.taskgroup.tasks.push(singlePromise.data.task);
});
apiFactory.createTaskGroup(dvm.taskgroup, function(response) {
$mdDialog.hide(dvm.taskgroup);
});
});
};
I have array of objects and want to execute some requests - one for every object.
How to achieve it, since $http-service executes request in async mode?
Like async/await in C#
You can call the next request in the callback of the $http.
Something like this :
function sendRequestList(objectList) {
if (objectList.length > 0) {
var currentObject = objectList.pop();
$http.get(/* request params here */)
.then(
function() {
sendRequestList(objectList);
},
function() {
sendRequestList(objectList);
}
);
}
}
However, I don't know a way do achieve this the way you want.
Hope this help.
A+
If you want all the requests to be finished before going on, you could use angular's $q service to generate a promise, and return a result only when every request is done.
// all of this in a controller injecting $q
function fetchAll(objects) {
var deferred = $q.defer();
var done = 0;
var results = {};
for(var i=0; i < objects.length - 1; i++) {
// a trick to avoid every iteration to share same i value
(function(index) {
$http.get(/* request params here */)
.then(
function(data) {
results[index] = data;
// or results[object.anyProperty] = data;
done++;
// ensure all calls are successful
if (done === objects.length) {
deferred.resolve();
}
},
function() {
deferred.reject();
}
);
})(i);
}
return deferred.promise;
}
// and call it like this
fetchAll(objects)
.then(function success(result) {
// continue your business
}, function error(result) {
// handle error
});
I'm trying to create an object by forEach on the responses of various services .. and I do not get the results I need.
I know I should use internal iterations promises but do not know how yet ..
EDIT:
well .. I need is to do is to fill an object of the data into $ foreach foreach .. http into a iteration promises, sample code:
someservice1.getitem().then(function(){
var dataInfo= {};
angular.foreach(data, function(v, k){
dataInfo[v]=[]
someservice2.getitem2(k.data).then(function(data){
datainfo[v].push(data)
})
})
$scope.dataInfo = datainfo;
})
so not working very well .. I think you can solve by using promises but I could not do it.
I hope your help
The problem is that the final $scope.dataInfo = datainfo; is executing early before the datainfo object gets completed. You need to create a promise from which that operation can chain safely.
finalPromise.then ( function (datainfo) {
$scope.dataInfo = datainfo;
});
You create finalPromise by returning and chaining from previous promises.
var service1Promise =
someservice1.getitem().then(function(data){
var promiseList = [];
angular.foreach(data, function(v,k){
var p = someservice2.getitem2(k.data);
p.then(function(data) {
//return key and data for chaining
return { key: k, data: data };
}) .catch (function (error) {
//throw key and error to chain rejection
throw { key: k, error: error };
});
//push to list
promiseList.push(p);
});
//return $q.all promise for chaining
return $q.all(promiseList);
});
Create finalPromise by chaining from service1Promise
var finalPromise =
service1Promise.then(function(fulfilledList) {
var dataInfoObj = {};
//assemble data info
angular.forEach(fulfilledList, function (fulfilledItem) {
dataInfoObj[fulfilledItem.key] = fulfilledItem.data;
});
//return datainfoObj for chaining
return datainfoObj;
});
Now you can chain from the finalPromise to put the data on $scope
finalPromise.then ( function onFulfulled (datainfo) {
$scope.dataInfo = datainfo;
}).catch (function onRejection (error) {
console.log("finalPromise rejected");
console.log(error.key);
console.log(error.error);
});
Be aware that $q.all is not resilient. If any of the promises are rejected, $q.all will be rejected with the first rejection.
For more information on chaining promises see the AngularJS $q Service API Reference -- chaining promises.
You are using angularjs, so with $q, for array of promises within promises you could make your code into something like this:
// first add $q service to your controller,
someservice1.getitem()
.then(function(data){
var dataInfo= {};
var promises = angular.map(data, function(item, index){
var promises2 = angular.map(item.data, function(iData){
someservice2.getitem2(iData)
})
return $q.all(promises2).then(function(resultArray){
dataInfo[index] = resultArray;
});
});
return $q.all(promises).then(function(){
console.log('all data are retrived...');
$scope.dataInfo = datainfo;
});
});
the same might look more elegant in ES6:
let serviceCall2 = data => someservice2.getitem2(data); // serviceCall2 is just a closure for someservice2.getitem2() call
someservice1.getitem()
.then(data => {
let dataInfo= {},
promises = data.map((item, index) => {
let promises2 = item.data.map(serviceCall2);
return $q.all(promises2).then(resultArray => dataInfo[index] = resultArray);
});
return $q.all(promises)
.then(() => {
console.log('all data are retrived...');
$scope.dataInfo = datainfo;
});
});
Is there a way to return an HttpPromise (or something similar) to mimic a call to $http? I want to set a global variable that indicates whether the real HTTP request is made or whether a fake HttpPromise object is returned with fake data.
For example, I have a service that is similar to this:
angular
.module('myservice')
.factory('MyService', ['$http', function($http) {
return {
get : function(itemId) {
if (isInTestingMode) {
// return a promise obj that returns success and fake data
}
return $http.get("/myapp/items/" + itemId);
}
};
} ]);
And in my controller, I have a call to the aforementioned service that looks similar to this:
// Somewhere in my controller
MyService.get($scope.itemId)
.success(function(data) {
$scope.item = data;
})
.error(function(data, status, headers, config) {
$scope.notFound = true;
});
I'm trying to not change the controller code; I want the success and error chaining to still work when in my "isInTestMode".
Is it possible to fake an HttpPromise in the way that I described in the service?
Below is a revised edition of the "MyService" above (a snippet) containing a success and error on the promise object. But, how do I execute the success method?
return {
get : function(itemId) {
if (isInTestingMode) {
var promise = $.defer().promise;
// Mimicking $http.get's success
promise.success = function(fn) {
promise.then(function() {
fn({ itemId : "123", name : "ItemName"}, 200, {}, {});
});
return promise;
};
// Mimicking $http.get's error
promise.error = function(fn) {
promise.then(null, function(response) {
fn("Error", 404, {}, {});
});
return promise;
};
return promise;
}
return $http.get("/myapp/items/" + itemId);
}
}
Just use the deferred method of the $qservice
var fakeHttpCall = function(isSuccessful) {
var deferred = $q.defer()
if (isSuccessful === true) {
deferred.resolve("Successfully resolved the fake $http call")
}
else {
deferred.reject("Oh no! Something went terribly wrong in your fake $http call")
}
return deferred.promise
}
And then you can call your function like an $http promise (you have to customize whatever you want to put inside of it, of course).
fakeHttpCall(true).then(
function (data) {
// success callback
console.log(data)
},
function (err) {
// error callback
console.log(err)
})
I found that this post is similar to what I was asking.
However, I wanted a way to mock my service call so that fake data could be returned instead of issuing a true HTTP request call. The best way to handle this situation, for me, is to use angular's $httpBackend service. For example, to bypass a GET request to my "items" resource BUT to not bypass GETs of my partials/templates I would do something like this:
angular
.module('myApp', ['ngMockE2E'])
.run(['$httpBackend', function($httpBackend) {
$httpBackend
.whenGET(/^partials\/.+/)
.passThrough();
$httpBackend
.whenGET(/^\/myapp\/items\/.+/)
.respond({itemId : "123", name : "ItemName"});
}]);
See this documentation for more information on $httpBackend.
I finally found a way using jasmin. $httpBackend was no option for me, as there were also non-$http-methods I needed mock on the same service. I also think that the controller test needing to specify the url is not perfect as imho the controller and its test should not need to know about it.
Here is how it works:
beforeEach(inject(function ($controller, $rootScope, $q) {
scope = $rootScope.$new();
mockSvc = {
someFn: function () {
},
someHttpFn: function () {
}
};
// use jasmin to fake $http promise response
spyOn(mockSvc, 'someHttpFn').and.callFake(function () {
return {
success: function (callback) {
callback({
// some fake response
});
},
then: function(callback) {
callback({
// some fake response, you probably would want that to be
// the same as for success
});
},
error: function(callback){
callback({
// some fake response
});
}
}
});
MyCtrl = $controller('MyCtrl', {
$scope: scope,
MyActualSvc: mockSvc
});
}));
You can implement your FakeHttp class:
var FakeHttp = function (promise) {
this.promise = promise;
this.onSuccess = function(){};
this.onError = function(){};
this.premise.then(this.onSuccess, this.onError);
};
FakeHttp.prototype.success = function (callback) {
this.onSuccess = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSucess, this.onError);
return this;
};
FakeHttp.prototype.error = function (callback) {
this.onError = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSuccess, this.onError);
return this;
};
Then in your code, you would return a new fakeHttp out of the promise.
if(testingMode){
return new FakeHttp(promise);
};
The promise must be asynchronous, otherwise it won't work. For that you can use $timeout.
easy peasy!
You can do it using angular-mocks-async like so:
var app = ng.module( 'mockApp', [
'ngMockE2E',
'ngMockE2EAsync'
]);
app.run( [ '$httpBackend', '$q', function( $httpBackend, $q ) {
$httpBackend.whenAsync(
'GET',
new RegExp( 'http://api.example.com/user/.+$' )
).respond( function( method, url, data, config ) {
var re = /.*\/user\/(\w+)/;
var userId = parseInt(url.replace(re, '$1'), 10);
var response = $q.defer();
setTimeout( function() {
var data = {
userId: userId
};
response.resolve( [ 200, "mock response", data ] );
}, 1000 );
return response.promise;
});
}]);