DEV Community

Ben Halpern
Ben Halpern

Posted on

Stamping out a funky Google Analytics race condition

We were seeing this error on some pages on dev.to

    Uncaught TypeError: ga is not a function

ga is the Google Analytics function. Typically you'll see this kind of error if you try to call code from a library that has not been loaded, but this was only happening on some pages, and the script is loaded the same way on all pages, with Google Analytics' script tag included in all pages the same way like such:

(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');

What's more, I realized that the error was not happening on "some" pages and not others, it was only happening on one page, the comments page for the question Which editor/IDE do you use and why?. I figured there must be some custom JavaScript loaded on that page that is interfering with the script somehow, but after digging around, I could not find any evidence of this.

What else is unique about this page?

This page in that it is the largest page on the site so far. With 202 comments and counting and no pagination, there are a lot of Dom nodes being loaded. Because this site eliminates all render-blocking latency, I suspect there might be a race condition wherein an async script might become executable in some order that messes with things. I'm really not sure what's going on, but the size of the page does mean that it renders in chunks, and the async script is able to fire before it "should" in Google Analytics world.

I'm really not sure what the real reason is, but since it really sucks to drop reports on visitor data, I scrambled to make it work.

I decided to add a setInterval() call that kept retrying until ga was defined.

var waitingOnGA = setInterval(function(){
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
    if(typeof ga === "function"){
        ga('create', 'UA-XXXXX-Y', 'auto');
        ga('send', 'pageview');
        clearInterval(waitingOnGA);
    }
},40)

Every 40 milliseconds (arbitrary), we ask if typeof ga === "function" and wait to fire any ga events. If ga exists and is a function, we clear the interval. The hack works! It was the fastest thing I could do to get reporting back up. I'll investigate further in the future to find out if this even was the problem. If anyone has any insight into the problem or any suggestions about how to do something less hacky in the meantime, I'd love to hear about it, please reply!

Top comments (0)