AngularJS - Set promise manually - angularjs

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

Related

Resolving a promise array

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

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 - how to perform query one by one?

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

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.

AngularJS: $q wait for all even when 1 rejected

I've been trying to wait for a couple of promises with Angular's $q but there seems to be no option to 'wait for all even when a promis is rejected'.
I've created an example (http://jsfiddle.net/Zenuka/pHEf9/21/) and I want a function to be executed when all promises are resolved/rejected, is that possible?
Something like:
$q.whenAllComplete(promises, function() {....})
EDIT: In the example you see that the second service fails and immediately after that the function in $q.all().then(..., function(){...}) is being executed. I want to wait for the fifth promise to be completed.
Ok, I've implemeted a basic version myself (I only want to wait for an array of promises). Anyone can extend this or create a cleaner version if they want to :-)
Check the jsfiddle to see it in action: http://jsfiddle.net/Zenuka/pHEf9/
angular.module('test').config(['$provide', function ($provide) {
$provide.decorator('$q', ['$delegate', function ($delegate) {
var $q = $delegate;
// Extention for q
$q.allSettled = $q.allSettled || function (promises) {
var deferred = $q.defer();
if (angular.isArray(promises)) {
var states = [];
var results = [];
var didAPromiseFail = false;
if (promises.length === 0) {
deferred.resolve(results);
return deferred.promise;
}
// First create an array for all promises with their state
angular.forEach(promises, function (promise, key) {
states[key] = false;
});
// Helper to check if all states are finished
var checkStates = function (states, results, deferred, failed) {
var allFinished = true;
angular.forEach(states, function (state, key) {
if (!state) {
allFinished = false;
}
});
if (allFinished) {
if (failed) {
deferred.reject(results);
} else {
deferred.resolve(results);
}
}
}
// Loop through the promises
// a second loop to be sure that checkStates is called when all states are set to false first
angular.forEach(promises, function (promise, key) {
$q.when(promise).then(function (result) {
states[key] = true;
results[key] = result;
checkStates(states, results, deferred, didAPromiseFail);
}, function (reason) {
states[key] = true;
results[key] = reason;
didAPromiseFail = true;
checkStates(states, results, deferred, didAPromiseFail);
});
});
} else {
throw 'allSettled can only handle an array of promises (for now)';
}
return deferred.promise;
};
return $q;
}]);
}]);
Analogous to how all() returns an array/hash of the resolved values, the allSettled() function from Kris Kowal's Q returns a collection of objects that look either like:
{ state: 'fulfilled', value: <resolved value> }
or:
{ state: 'rejected', reason: <rejection error> }
As this behavior is rather handy, I've ported the function to Angular.js's $q:
angular.module('your-module').config(['$provide', function ($provide) {
$provide.decorator('$q', ['$delegate', function ($delegate) {
var $q = $delegate;
$q.allSettled = $q.allSettled || function allSettled(promises) {
// Implementation of allSettled function from Kris Kowal's Q:
// https://github.com/kriskowal/q/wiki/API-Reference#promiseallsettled
var wrapped = angular.isArray(promises) ? [] : {};
angular.forEach(promises, function(promise, key) {
if (!wrapped.hasOwnProperty(key)) {
wrapped[key] = wrap(promise);
}
});
return $q.all(wrapped);
function wrap(promise) {
return $q.when(promise)
.then(function (value) {
return { state: 'fulfilled', value: value };
}, function (reason) {
return { state: 'rejected', reason: reason };
});
}
};
return $q;
}]);
}]);
Credit goes to:
Zenuka for the decorator code
Benjamin Gruenbaum for pointing me in the right direction
The all implementation from Angular.js source
The promise API in angularJS is based on https://github.com/kriskowal/q. I looked at API that Q provides and it had a method allSettled, but this method has not been exposed over the port that AngularJS uses. This is form the documentation
The all function returns a promise for an array of values. When this
promise is fulfilled, the array contains the fulfillment values of the
original promises, in the same order as those promises. If one of the
given promises is rejected, the returned promise is immediately
rejected, not waiting for the rest of the batch. If you want to wait
for all of the promises to either be fulfilled or rejected, you can
use allSettled.
I solved this same issue recently. This was the problem:
I had an array of promises to handle, promises
I wanted to get all the results, resolve or reject
I wanted the promises to run in parallel
This was how I solved the problem:
promises = promises.map(
promise => promise.catch(() => null)
);
$q.all(promises, results => {
// code to handle results
});
It's not a general fix, but it is simple and and easy to follow. Of course if any of your promises could resolve to null then you can't distinguish between that a rejection, but it works in many cases and you can always modify the catch function to work with the particular problem you're solving.
Thanks for the inspiration Zenuka, you can find my version at https://gist.github.com/JGarrido/8100714
Here it is, in it's current state:
.config( function($provide) {
$provide.decorator("$q", ["$delegate", function($delegate) {
var $q = $delegate;
$q.allComplete = function(promises) {
if(!angular.isArray(promises)) {
throw Error("$q.allComplete only accepts an array.");
}
var deferred = $q.defer();
var passed = 0;
var failed = 0;
var responses = [];
angular.forEach(promises, function(promise, index) {
promise
.then( function(result) {
console.info('done', result);
passed++;
responses.push(result);
})
.catch( function(result) {
console.error('err', result);
failed++;
responses.push(result);
})
.finally( function() {
if((passed + failed) == promises.length) {
console.log("COMPLETE: " + "passed = " + passed + ", failed = " + failed);
if(failed > 0) {
deferred.reject(responses);
} else {
deferred.resolve(responses);
}
}
})
;
});
return deferred.promise;
};
return $q;
}]);
})
A simpler approach to solving this problem.
$provide.decorator('$q', ['$delegate', function ($delegate) {
var $q = $delegate;
$q.allSettled = $q.allSettled || function (promises) {
var toSettle = [];
if (angular.isArray(promises)) {
angular.forEach(promises, function (promise, key) {
var dfd = $q.defer();
promise.then(dfd.resolve, dfd.resolve);
toSettle.push(dfd.promise);
});
}
return $q.all(toSettle);
};
return $q;
}]);
A simple solution would be to use catch() to handle any errors and stop rejections from propagating. You could do this by either not returning a value from catch() or by resolving using the error response and then handling errors in all(). This way $q.all() will always be executed. I've updated the fiddle with a very simple example: http://jsfiddle.net/pHEf9/125/
...
function handleError(response) {
console.log('Handle error');
}
// Create 5 promises
var promises = [];
var names = [];
for (var i = 1; i <= 5; i++) {
var willSucceed = true;
if (i == 2) willSucceed = false;
promises.push(
createPromise('Promise' + i, i, willSucceed).catch(handleError));
}
...
Be aware that if you don't return a value from within catch(), the array of resolved promises passed to all() will contain undefined for those errored elements.
just use finally
$q.all(tasks).finally(function() {
// do stuff
});

Resources