0

I am using Googles Analytics api in Javascript.

They provide a method that gets the result of a list, once the execution is done it calls a callback method with the result.

Looks something like this:

gapi.client.analytics.management.accounts.list().execute(handleAccounts);

...

function handleAccounts(results) {
        if (!results.code) {
        if (results && results.items && results.items.length) {

// See this code below.

        } else {
            console.log('No accounts found for this user.')
        }
    } else {
        console.log('There was an error querying accounts: ' + results.message);
    }
}

Generally this is fantastic, however... I need a flattened list of all child items so I keep calling like this:

 $.each(results.items, function (index, value) {

gapi.client.analytics.management.webproperties.list({ 'accountId': value.Id}).execute(handleWebproperties);
// and so on..
            })

The problem being if at any level you have more than one item you end up with multiple asynchronous calls shooting off, and I won't know when they have all finished to get the final list.

Other than writing something to keep track of how many calls have been made then waiting for that total to come back.

How can I easily know when they have all completed?

Thanks.

In summary:

A user can have multiple accounts, accounts can have multiple properties and properties can have multiple profiles.

I need all the profiles for all the accounts of a user.

shenku
  • 11,969
  • 12
  • 64
  • 118
  • See this: http://stackoverflow.com/questions/4631774/coordinating-parallel-execution-in-node-js/4631909#4631909 – slebetman Oct 08 '12 at 05:04

3 Answers3

0

The way to solve this is to keep track of how many asynch calls you've made (or are going to make) and in each response you receive decrement that count and check to see if the last one is now done. It is important to make sure you have robust error handling in case one response generates an error and doesn't complete successfully.

You seem to know this is an option and it's unclear why you aren't pursuing this, but it is the way to solve this. It's also possibly to use jQuery deferreds to keep track of when the last one is done, but that's just using a library to do your counting for you - it's still fundamentally the same thing underneath.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Yup, I just feel like there should be a simpler way of doing this? Or a JQuery component or something that can wrap it neatly for me? – shenku Oct 08 '12 at 04:55
  • @shenku - As I've added to my answer, you can use [jQuery deferreds](http://api.jquery.com/category/deferred-object/) if you'd rather use a library to keep track of this for you. – jfriend00 Oct 08 '12 at 04:58
0

Based on my fork function which I wrote as a previous answer to this: Coordinating parallel execution in node.js. I would do it like this:

var queryFunctions = []
$.each(results.items, function (index, value) {
    queryFunctions.push(function(callback){
        gapi.client.analytics.management.
            webproperties.list({ 'accountId': value.Id}).
            execute(callback);
    });
})

fork(queryFunctions,function(result){
    // This is the final callback, so do processing here
})

The implementation of fork is simply this:

function fork (async_calls, shared_callback) {
  var counter = async_calls.length;
  var all_results = [];
  function makeCallback (index) {
    return function () {
      counter --;
      var results = [];
      // we use the arguments object here because some callbacks 
      // in Node pass in multiple arguments as result.
      for (var i=0;i<arguments.length;i++) {
        results.push(arguments[i]);
      }
      all_results[index] = results;
      if (counter == 0) {
        shared_callback(all_results);
      }
    }
  }

  for (var i=0;i<async_calls.length;i++) {
    async_calls[i](makeCallback(i));
  }
}

As you can see, all it does is keep track of how many calls have been made in the counter variable. But it's all encapsulated in a single function so you can re-use the logic. Also, I think it makes the code more tidy.

Community
  • 1
  • 1
slebetman
  • 109,858
  • 19
  • 140
  • 171
0

You can use the jQuery Deferred features to do this sort of thing. Basically if you wrap the callbacks in a Deferred object, then use $.when(...) to group them all together.

Something like this (the exact structure that will make the most sense for you will depend on how you're using it, this code assumes you need to do something specific in each callback as well as know when they're all done):

var promises = [];
$.each(results.items, function (index, value) {
    var deferred = new $.Deferred();
    gapi.client.analytics.management.webproperties.list({ 'accountId': value.Id}).execute(function(results) {
        handleWebproperties(results);
        deferred.resolve();
    });
    promises.push(deferred.promise());
    // and so on...
});

$.when.apply(this, promises).done(function() {
    alert("Everything is finished!");
});

Note I'm just coding straight into the browser window, so this may not be 100% functioning. :)

Alconja
  • 14,834
  • 3
  • 60
  • 61
  • @shenku - You using jQuery (v1.5 or above)? – Alconja Oct 08 '12 at 05:49
  • Not to worry jquery just hasn't loaded quick, will implement now and let you know how it goes... – shenku Oct 08 '12 at 21:20
  • the only problem I am having with this is then 'when.apply' always gets executed when it hits that statement even if there are unresolved promises? – shenku Oct 08 '12 at 22:13
  • @shenku - The `when.apply` will be called immediately, but jQuery won't call the `.done(function() { ... });` until all promises are resolved. – Alconja Oct 08 '12 at 22:25
  • done is getting called immediately, maybe because all the promises i expect have not yet made it to the array? See the code here, i've taken the noise out... http://pastebin.com/DG3sAFjJ – shenku Oct 08 '12 at 22:32
  • Yikes, your code is going async like three layers deep. The challenge here (and the reason your pastebin isn't working) is that the `.when` call doesn't even know about all the promises because some of them aren't even made until after other async calls complete. You'll need to layer the calls to build up multiple `.when` style groupings so that everything finally gets aggregated to the top in one final ultimate deferred wrapper. See updated pastebin here: http://pastebin.com/cvCwQmYH – Alconja Oct 08 '12 at 23:45
  • yea, that is what I was trying to say (hard to explain though) thanks for your help lets see how I go with those changes... – shenku Oct 08 '12 at 23:47