1

I am having trouble figuring out proper use of angular's $q and Javascript's promise api. Then again, I am pretty new to Javascript, and may be making a syntax error.

This question seems similar, but it doesn't seem to be an exact copy...

My service has a method like this (called by my controller):

self.getResources = function() {

    let promises = [];

    for (let resourceName in $rootScope.resourceNameList) {

        promises.push($http({ 
            url : '/' + resourceName,
            method : 'GET' 
        }).then(function (res) { 
            return { resourceName : res.data};
        }));
    }
    return promises;
};

And my controller passes the returned promise list into angular's $q.all():

let promises = resourceService.getResources();

$q.all(promises).then(function (resultsArray) {

    // resultsArray should be an array of elements like:
    // [ {cars : [...]}, {coffeePots : [...]}, {motorBoats : [...]} ]
    // with size = resourceNameList.length(), correct? 

    // this log is never called.
    console.log(resultsArray);
    $scope.resources = resultsArray;
});

Finally, in my HTML, I try to iterate over and display the resources:

.
.
.
<!-- 'cars' is and example, but I am trying to retrieve this data in the same way -->
<tr ng-repeat="item in resources[cars]">
    <td>{{item.property1}}</td>
    <td>{{item.property2}}</td>
</tr>

The table is not displaying any results.

I can see from the console that the HTTP requests are successful and returning data, so I must be making a mistake with either the promise or $q apis.

Any suggestions?

Thanks much.

Community
  • 1
  • 1
Mac
  • 1,143
  • 6
  • 21
  • 45
  • You aren't doing any catches, so you could also be swallowing an error upstream. – barry-johnson May 25 '16 at 22:31
  • 1
    you return object with property `resourceName ` in service method but aren't accounting for that in controller. Log `resourcesArray` to console and inspect structure. You have more manipulation to do to make it all one array – charlietfl May 25 '16 at 22:32
  • does $q.all resolve? e.g. have you put a console.log in that block and seen that it's getting called and aht resultsArray looks like? – dewd May 25 '16 at 22:34
  • Good eyes @charlietfl. I glossed over the literal use when I scanned it. – barry-johnson May 25 '16 at 22:35
  • resultsArray =/= resourcesArray – Seth Malaki May 25 '16 at 22:40
  • @dewd @charlietfl i added a `console.log()` to the `$q.all().then(){}` block, and no, it isn't resolving, so I can't see how it looks at the moment... – Mac May 25 '16 at 22:40
  • @Seth sorry about that - just fixed it – Mac May 25 '16 at 22:42
  • 1
    Thanks. now try adding a `.catch` after then – Seth Malaki May 25 '16 at 22:47
  • 1
    @Seth I think i just figured it out. One of the resources in `resourceNamesList` was just a test value. It was getting a 404 for obvious reasons, and i think that makes the whole off `$q.all()` fail. I will look into it and write something soon. – Mac May 25 '16 at 22:51
  • 4
    yes that is exactly why you need to add a `catch` after `q.all`. always do that to detect promise resolution problems early. – Seth Malaki May 25 '16 at 22:53
  • 1
    @Mac Correct. `$q.all()` only resolves once all promises within it have resolved, e.g. if you only get 99 out of 100, it's then won't get called. – dewd May 26 '16 at 11:47
  • @SethMalaki A good suggestion. Could you advise where you would put the catch in @Mac's code above? `$q.all()` takes only an array/ object of promises as its only argument. Would you wrap the `$q.all()` or have it in the `self.getResources` function's `for` loop e.g. `try{$http(...`? – dewd May 26 '16 at 11:54
  • 1
    no not try/catch but `$q.catch`. See the angular docs for `$q`.. I'd link you but i am mobile right now – Seth Malaki May 26 '16 at 11:57
  • 1
    @SethMalaki when you're able to I think you should add an answer with this solution. I for one would certainly upvote it. – dewd May 26 '16 at 12:02
  • Done. but I'm not sure if it technically qualifies as an answer ¯\\_(ツ)_/¯ – Seth Malaki May 26 '16 at 12:49
  • @Mac do you mind if I change the title of your question to include " - Using q.catch to trap promise errors"? This is essentially what you needed to do, and adds relevance to the correct answer. – dewd May 26 '16 at 13:35

2 Answers2

2

I know this is probably a non-answer since you've already figured that you actually have a mistake on your input data set, but you should really detect those kinds of problems earlier by adding a .catch on (at least) your root promise so you can immediately see what's actually up.

In your case, this is your root promise (with the catch already attached). I've broken it into newlines for clarity:

let promises = resourceService.getResources();

$q
 .all(promises)

 // unfortunately, .then only gets executed if all of these promises succeed.
 .then(function (resultsArray) {
   console.log(resultsArray);
   $scope.resources = resultsArray;
 })

 // there is a disturbance in the force!
 .catch(function (error) {
    // you can console.log(error) OR
    window.open("https://stackoverflow.com/search?q="+error.message);
 })
 ;

EDIT: If you need to filter out the failed promises on $q.all, just replace it with $q.allSettled! Full API Here: https://github.com/kriskowal/q

Some additional thoughts to share: soon enough, you'll find yourself complaining $q to be missing some features like combining .push or .map with .all and such. There's already a library for that; you can use BluebirdJS! and here's a nifty wrapper for it for angular. instead, your code would look like this:

Promise.map($rootScope.resourceNameList, function(resourceName) {
      return $http({ 
        url : '/' + resourceName,
        method : 'GET' 
      });
}, {concurrency: 3}) // did I mention you could control concurrency?
.then(function(results) {
  console.log(results); // $http promises that were resolved!
},function(rejects) {
  console.log(rejects); // $http promises that were rejected :(
})
.catch(function (error) {
    // you can console.log(error) OR
    window.open("https://stackoverflow.com/search?q="+error.message);
})
;

Much better, right?

Happy coding! Here's a great read about promises: http://taoofcode.net/promise-anti-patterns/

Seth Malaki
  • 4,436
  • 23
  • 48
  • Out of curiosity, do you know if there is a way to change the fail functionality of `$q.all()` ? Or at least know of another way to execute a series of `$http` calls in the way I described, but ignore the "dropped" requests? Thanks to you both for the help so far – Mac May 26 '16 at 14:53
  • 1
    Yep, It's `q.allSettled`. Full API here: https://github.com/kriskowal/q (You'll notice, on the angular repository, `package.json` has a reference to 'q' as a dependency. It points exactly to this repository). – Seth Malaki May 26 '16 at 14:56
  • I realize I need to edit my answer to be much more concise. Thanks for reminding me of `.allSettled`! – Seth Malaki May 26 '16 at 14:59
  • No problem. The angular docs is quite good at hiding small, but important details. You can see a tiny mention about the Q library just a hair above the Deferred API section. Enjoy! Promises are awesome. – Seth Malaki May 26 '16 at 15:06
0

You need to inject $q and $http in the directive like this

self.getResources = function($q,$http) {

let promises = [];

for (let resourceName in $rootScope.resourceNameList) {

    promises.push($http({ 
        url : '/' + resourceName,
        method : 'GET' 
    }).then(function (res) { 
        return { resourceName : res.data};
    }));
}
return promises;

};

zeeshan Qurban
  • 387
  • 1
  • 3
  • 15