Resolving a promise array - angularjs

I am having trouble in resolving a promise that is returned by firebase. This is an ionic - angularjs - firebase project that I am building to learn. The issue is that my function returns a promise that contains an array of 3 users but I am unable to unwrap this promise.
My code:
function eventusers(id) {
var userarr = [];
var deferred = $q.defer();
// *The code below makes 2 firebase calls and returns an array of users*
eventref.orderByChild("Eventid").equalTo(eventid).on("value", function(snap) {
var users = snap.val();
angular.forEach(users, function(value,key) {
var obj = value;
for (var prop in obj) {
if(obj[prop] == "True") {
userref.child(prop).on("value", function (snap) {
var id = snap.val().email;
userarr.push(id);
console.log(userarr); // I am able to see the list of users here
});
};
}
});
deferred.resolve(userarr);
});
return deferred.promise;
};
//The console.log shows a promise (pls see the attached pic)
console.log(eventusers(eventid));
// I tried to loop through the response using angular.forEach and also a for loop but it does
//not execute that part of the code as I do not see the response of the console.log. If I
//replace the for loop with just console.log(response), then I get an empty array.
eventusers(eventid).then(function (response) {
for (var i = 0; i <response.length; i++) {
console.log(response[i]);
}
});

Your deferred promise is resolving before the inner asynchronous action
userref.child(prop).on('value', ...
completes.
You'll need to wrap that in another deferred object then return a promise resolving all of the inner promises.
function eventusers(id) {
return $q(function(resolve) {
eventRef.orderByChild('Eventid').equalTo(id).on('value', function(snap) {
var promises = [];
snap.val().forEach(function(user) {
angular.forEach(user, function(userProp, prop) {
if (userProp === 'True') {
promises.push($q(function(resolve) {
userref.child(prop).on('value', function(snap) {
resolve(snap.val().email);
});
}));
}
});
});
resolve($q.all(promises));
});
});
}
eventusers(eventid).then(function (response) {
for (var i = 0; i < response.length; i++) {
console.log(response[i]);
}
});

Modify your code to
eventusers(eventid).then(function (response) {
var myArray = response.value;
for (var i = 0; i <myArray.length; i++) {
console.log(myArray[i]);
};
Since your array object is inside of promise object
You can also refer to Plunker

Related

wait for async http requests before proceeding angular

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);
});
});
};

AngularJS - Set promise manually

In AngularJSwe work a lot with promises, and I was wondering if there is some way to manually set the result of promise (whether it was a success or not) manually without reject and resolve
Something like
var list = [];
function call(){
async()
.then(function(response){
console.info('yay');
})
.catch(function(error){
console.info('nay');
});
}
function async(){
var item = {
id: 1,
defer: $q.defer()
};
list.push(item);
sendRequestToAsyncService(item.id);
return item.defer.promise;
}
function receiveDataFromAsyncService(data, id){
for(var i = 0; i < list.length; i++){
if(id === list[i].id){
list[i].defer.promise = data;
}
}
}
After fiddling for a while I found the answer, I had to use the resolve/reject functions at the defer root level.
list[i].defer.resolve(response);

Angular undefined promise

I'm new in Angular, and maybe I don't understand everything about promise, so...
I have a resource factory
.factory('Product',["$resource", function ($resource){
var Resource = $resource(
"/api/product/:product_id/",
{product_id: '#id'},
{
query: {
isArray: true,
transformResponse: function (data) {
var items = angular.fromJson(data);
return items.results;
}
},
update: {
method: "PUT",
}
},
{
stripTrailingSlashes: false
}
);
And in my other factory.
.factory('Get',["$http", "Product", function ($http, Product){
return {
getDesc: function(id){
var allProducts = Product.query();
var products = [];
for (var i = 0; i < allProducts.length; i++){
if(allProducts[i].desc == id){
products.push(allProducts[i]);
}
}
return products;
}
}
}])
And in my Controller
$scope.someClick = function(product){
var products = Get.getDesc(product.id);
products.$promise.then(function(data){
$log.log(data);
});
};
And I got: Error: r.$promise is undefined
I don't know why. Could you help me?
There are a few issues with your code. Specifically with the promise:
The query method returns a resource that contains in itself a $promise. so you can do one of the following:
1. Return the promise and do the processing, the filtering (more on that later) in the controller.
2. Use .then on the promise to do the filtering and return that promise:
getDesc: function(id){
var allProductsPromise = Product.query().$promise;
return allProductsPromise.then(function(response){
var products = [];
for (var i = 0; i < response.length; i++){
if(response[i].desc == id){
products.push(response[i]);
}
}
return products;
});
}
The other issue I see is the double filtering. Since your query takes an id, I assume you get only the relevant product. Why do you have to filter the results (iterate and compare the id)? If my observation is correct, you might not need the filtering block and you can return directly the original promise.

Angular & Jasmine: how to test that a $q promise chain was resolved

I have a Service that expose a function that receives a parsed CSV (using papaparse) and promise that reflects the parsing status:
If the file was missing mandatory fields, the promise is rejected
Otherwise, It parses each row into an item and auto populates the missing fields (the auto population process is asynchronous).
when all items are populated, the function resolves the promise with the items array
The function I want to test is onCsvParse:
angular.module('csvParser', [])
.factory('csvParser', ['$http',
function($http) {
var service = {
onCsvParse: function(results, creatingBulkItems) {
var errors = this.getCsvErrors(results);
if (errors.length > 0) {
//reject
creatingBulkItems.reject(errors);
} else {
var items = this.parseCsv(results);
var autoPopulateItems = [],
populatedItems = [];
for (var i = 0; i < populatedItems.length; i++) {
var item = items[i];
if (item.name === "" /*or some any field is missing */ ) {
// auto populate item
autoPopulateItems.push(this.autoPopulateItem(item));
} else {
var populatedItem = $q.when(item);
populatedItems.push(populatedItem);
}
}
populatedItems =autoPopulateItems.concat(populatedItems);
var populatingAllItems = $q.all(populatedItems);
populatingAllItems.then(function(items) {
creatingBulkItems.resolve(items);
}, function(err) {
creatingBulkItems.resolve(err);
});
}
},
autoPopulateItem: function(newItem) {
var populatingItem = $q.defer();
var item = angular.copy(newItem);
$http.post('api/getItemData', { /*.....*/ })
.success(function(response) {
//----Populate item fields
item.name = response.name;
//....
//resolve the promise
populatingItem.resolve(item)
}).error(err) {
// resolving on error for $q.all indication
populatingItem.resolve(item)
};
return populatingItem.promise;
}
}
return service;
}
])
My test for this method looks as follows (simplified):
describe('bulk items upload test', function() {
//upload csv & test scenarios...
var $rootScope, $q, csvResults = {};
var $httpBackend, requestHandler;
beforeEach(module('csvParser'));
beforeEach(inject(function(_$rootScope_, _$q_) {
$rootScope = _$rootScope_;
$q = _$q_;
}));
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
requestHandler = $httpBackend.when('POST', 'api/getItemData')
.respond({
name: "name",
description: "description",
imageUrl: "www.google.com"
});
// afterEach(function(){ $rootScope.$apply();});
}));
it('Should parse csv string', function(done) {
var csvString = "Name,Description of the page";//...
Papa.parse(csvString, {
complete: function(results) {
csvResults = results;
done();
}
});
});
it('Should fail', function(done) {
var creatingBulkItems = $q.defer();
console.log("here..");
csvParser.onCsvParse(csvResults, creatingBulkItems);
creatingBulkItems.promise.then(function() {
console.log("1here..");
//promise is never resolved
expect(1).toEqual(1);
done();
}, function() {
//promise is never rejeceted
console.log("2here..");
expect(1).toEqual(1);
done();
});
$rootScope.$apply();
});
});
With this I get the error: Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
the promises are not resolved, although I called $rootScope.$apply() and I am also not calling a real asynchronous call (only mocks,except $q.all).
How can I make it work?
Invalid syntax. You need to pass a function to the error callback.
}).error(function(err) {
// resolving on error for $q.all indication
populatingItem.resolve(item)
});
return populatingItem.promise;
Also you jasime test require some more initialization:
http://plnkr.co/edit/wjykvpwtRA0kBBh3LcX3?p=preview
After reading this article: https://gist.github.com/domenic/3889970 I found out my problem.
The key point is to flatten the promise chains using the promise.then return value.
This [promise.then] function should return a new promise that is fulfilled when the given fulfilledHandler or errorHandler callback is finished. This allows promise operations to be chained together. The value returned from the callback handler is the fulfillment value for the returned promise. If the callback throws an error, the returned promise will be moved to failed state.
Instead of resolving the outer promise within the inner promise success/fail callbacks, the outer promise is resolved in the inner promise.then callbacks
So my fix is something like this:
onCsvParse: function(results) {
var errors = this.getCsvErrors(results);
if (errors.length > 0) {
var deferred = $q.defer();
//reject
return deferred.reject(errors);
} else {
var items = this.parseCsv(results);
var autoPopulateItems = [],
populatedItems = [];
for (var i = 0; i < populatedItems.length; i++) {
var item = items[i];
if (item.name === "" /*or some any field is missing */ ) {
// auto populate item
autoPopulateItems.push(this.autoPopulateItem(item));
} else {
var populatedItem = $q.when(item);
populatedItems.push(populatedItem);
}
}
populatedItems = autoPopulateItems.concat(populatedItems);
var populatingAllItems = $q.all(populatedItems);
return populatingAllItems.then(function(items) {
return items;
}, function(err) {
return err;
});
}
},
test code:
it('Should not fail :)', function(done) {
csvParser.onCsvParse(csvResults).then(function(items) {
//promise is resolved
expect(items).not.toBeNull();
done();
}, function() {
//promise is rejeceted
//expect(1).toEqual(1);
done();
});
$rootScope.$apply();});

TypeError: promises[i].done is not a function

I am returning an $http.get object from a service to a controller.
function in the geturl service -
this.fetch = function(index){
var url = some_url;
return $http.get(url,{timeout:8000});
};
In the controller I have --
var request = geturl.fetch(0);
request.success(function(data,status,headers,config){
// some logic
}).error(function(data,status,headers,config){
// some logic
});
$scope.promise.push(request); // $scope.promise is an array which contains all the promises
whenAll($scope.promise).done(function(){
});
function whenAll -
function whenAll(promises) {
var i, data = [],
dfd = $.Deferred();
for (i = 0; i < promises.length; i++) {
promises[i].done(function(newData) {
// something
}).fail(function() {
//something
});
}
return dfd.promise();
}
I am getting the following error -
TypeError: promises[i].done is not a function
If you want to execute something when all your promises resolve...
...you should take a look at $q.all()
Enjoy!
This is not the angular way I should say you must use $q service for it.:-
$q.all([Your array]).then(function(values) {
//Your code
});
Service:-
this.fetch = function(index){
var url = some_url;
var def = $q.defer();
$http.get(url,{timeout:8000}).success(function(data) {
def.resolve(data);
})
.error(function() {
def.reject("Failed to get albums");
});
return def.promise;;
};
Controller:-
var promises = [];
var promise1 = geturl.fetch(0);
var promise2 = geturl.fetch(1);
promises.push(promise1);
promises.push(promise2);
$q.all(promises).then(function(results){
});
Hope it help :)

Resources