02 June 2010

Google Analytics Asynchronous Tracking: How it works

The Google Analytics asynchronous tracking code came out of beta two weeks ago, and given my prior post on how scripts block page loads, I figured I’d briefly point out some interesting aspects of how the new tracking code works. The two things that I find interesting are (1) the asynchronous script loading and (2) the new syntax related to queuing events.


Which browsers support the async script attribute?

Here is the code that Google asks you to include, notice the “ga.async = true;” part:

var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXX-X']);
_gaq.push(['_trackPageview']);
(function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript';
    ga.async = true;
    ga.src = ('https:'   == document.location.protocol ? 'https://ssl'   : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();

It appears that setting the “async” attribute to true makes the magic happen, but if you look into which browsers currently support the async attribute, you’ll discover that it’s only Firefox 3.6+. So what gives?

It turns out that if you insert a script—that you created using document.createElement—dynamically into a page, then it will be asynchronous in most browsers. I ran some tests when this code was first released, and my results are included below.

Note: this table describes for each browser whether the the domContentLoaded event waits for an “async script” to finish before triggering, or if it goes ahead and triggers while the script is still loading. It does the same for window.load and also shows if a script that appears lower on the page will wait for an “async script” to load before executing or if it just goes ahead.

domContentLoaded event (jQuery) window.load event Execution of lower script on page
Mac:
Safari 4.0.4 goes waits goes
FF 3.5.7 waits waits waits
FF 3.6 goes waits goes
Opera 10.10 waits waits waits
Chrome goes waits goes
 
Windows:
IE6 goes goes goes
IE7 goes goes goes
IE8 goes goes goes
FF2 goes waits waits
FF3.6 goes waits goes
Chrome goes waits goes

As you can see, the most important actions—the domContentLoaded event firing and a lower script executing—can both happen before the “async script” finishes loading for all of these browsers except for Opera and Firefox less than 3.6. It’s mostly asynchronous… mostly.


The asynchronous syntax: _gaq.push()

There’s a new syntax for tracking events, and also for setting the initial variables (like your account id) which looks like this:

var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXX-X']);
_gaq.push(['_trackPageview']);

and:

<a href="#" onClick="_gaq.push(['_trackEvent', 'Videos', 'Play', 'How is babby formed?']);">Play</a>

The magic happens when the script actually loads and then replaces the _gaq array with a new object that immediately executes all previously queued events and has a “push” method that will also immediately execute any event whenever it is called. The code for the new _gaq object looks something like this (a little simplified):

var GoogleAnalyticsQueue = function () {

    this.push = function () {
        for (var i = 0; i < arguments.length; i++) try {
            if (typeof arguments[i] === "function") arguments[i]();
            else {
                // get tracker function from arguments[i][0]
                // get tracker function arguments from arguments[i].slice(1)
                // call it!  trackers[arguments[i][0]].apply(trackers, arguments[i].slice(1));
            }
        } catch (e) {}
    }

    // more code here…
};

// get the existing _gaq array
var _old_gaq = window._gaq;

// create a new _gaq object
window._gaq = new GoogleAnalyticsQueue();

// execute all of the queued up events - apply() turns the array entries into individual arguments
window._gaq.push.apply(window._gaq, _old_gaq);

It’s a pretty cool idea that should make Computer Science professors who love interfaces really happy. If you want to read some more about the asynchronous tracking code, Mathias Bynens wrote a great post describing how to optimize the asynchronous Google Analytics snippet—it has some real passion for minimizing bits.

Extra: the “apply” method

If you’re not familiar with the “apply” method, it can be called on any function and takes two arguments: (1) an object that should be treated as the this object inside the function and (2) an array that will be the arguments of the function. It’s being used in the above examples to convert arrays into the arguments of a function (almost like unpacking in Python, but not as elegant).

Also, if you want to use the “apply” method purely to set the this object inside a function, you should consider using “call” instead, which is slightly faster, but requires you to pass the arguments in directly (not in an array).

my_func.apply(this, ['foo', 'bar']);
my_func.call(this, 'foo', 'bar');

Comments (3)

1. Tomas Kapler wrote:

Thank you for this great explanation and test, i have one question - i got (and need) quite complex GA tracking extension (adding own search engines etc.) so i have always had own .js file on my server, which loaded after ga.js. I have rewriten this script so it uses new .push way, but i wander what would be the best way to add it to external file, so it will be loaded on the right time. Now I'm inserting it on every page, what is of course not optimal as it is not cached. My script is on visible on http://www.seo-konzultant.cz

Posted on 28 June 2010 at 6:06 PM  |  permalink

2. Jonathan wrote:

Excellent thanks very much Peter, I was wondering today how the push worked to generate a utm.gif request, and now I know.

The GA documentation was a little confusing, suggesting in all the examples that .push() calls had to come before the script injection function ran.

Posted on 29 June 2010 at 4:06 AM  |  permalink

3. peter wrote:

Hey Tomas, what are you trying to fix? You have that obfuscated/packed code inline on your pages inside the GA tracking snippet, and you have your “external-tracking.min.js” file that adds trackers to each link on a page. If you’re trying to get less js inline on your pages, I’m pretty sure that you could just move the entire inline snippet into your external tracking file, since you have to load that file anyways. You might want to test it before deploying though.

Also, does the external click tracking work reliably? I remember doing something similar where I ended up using onmousedown instead of onclick, because the new page would load before my tracking request would get sent. Maybe the GA script is doing a better job at sending tracking requests than I was…

Posted on 29 June 2010 at 11:06 AM  |  permalink

Peter Coles

Peter Coles

is a software engineer who lives in NYC, worked at Hunch/eBayNYC, and blogs here.
More about Peter »

@lethys · github · rss

It’s time to get big money out of politics. Join the kick-started campaign to put government back in the hands of the people. Pledge mayday.us now