One of the challenges in working with Google Tag Manager (or any JavaScript-based platform for that matter) is what to do with race conditions. A race condition emerges when you have two resources competing for execution in the browser, and there is a degree of unpredictability to which “wins” the race.

A prime example is working with jQuery. It’s one of the most popular JavaScript libraries out there, and websites utilize it for a multitude of things, many useful for Google Tag Manager, too. For example, jQuery trivializes asynchronous HTTP requests and DOM traversal, both of which can cause headaches to GTM users.

However, since jQuery is often, and should often be, downloaded asynchronously, there’s the risk that jQuery hasn’t loaded yet when GTM starts executing your tags. Thus we need some mechanism to let Google Tag Manager know when an asynchronously downloaded or requested resource has become available.

Tip 66: Add a load listener to script elements

There are two ways you can go about this. The first one is to use a Custom HTML tag, and then fire a dataLayer.push() once the resource has completely loaded. The second way is to use tag sequencing, and tell GTM that the setup tag (where you load the script) has completed by using the internal onHtmlSuccess() method. But I’m getting ahead of myself.

dataLayer.push() in the load listener callback

The first method is to create a Custom HTML tag that fires as early as possible. So you’d want to add the All Pages trigger to it, so that it fires as soon as the GTM container has loaded.

At this point it’s important that you do not load the script by simply adding the <script src="url_to_jquery" async="true"> into the Custom HTML tag. Instead, we’ll introduce some extra control by using JavaScript to create the element, add the listener to it, and inject it manually to the page. You could use the onload attribute directly in the element, but the risk here is that you overwrite any existing onload attribute. By using JavaScript DOM manipulation methods, you won’t mess with any pre-existing listeners.

<script>
  (function() {
    var el = document.createElement('script');
	el.src = 'https://code.jquery.com/jquery-3.2.1.js';
	el.async = 'true';
	el.addEventListener('load', function() {
	  window.dataLayer.push({
	    event: 'jQueryLoaded'
	  });
    });
    document.head.appendChild(el);
  })();
</script>

When this Custom HTML tag is run by GTM, the browser creates an asynchronous request to download the jquery-3.2.1.js from the CDN (Content Distribution Network). Once this asynchronous process is over, a dataLayer.push({event: 'jQueryLoaded'}) fires, and you can then build a Custom Event trigger for this event name to fire any tags that are dependent on jQuery having loaded.

Using Tag Sequencing

If you only have a single tag that needs the asynchronously downloaded resource, a fairly elegant way to do this would be with tag sequencing. With tag sequencing, you would create a Custom HTML tag for this code, and add it as the Setup tag in the sequence. The logic is that the asynchronous request is executed in the Setup tag, and once it’s complete, the main tag can fire with confidence that the resource it is dependent on has completely loaded.

This is what the Custom HTML tag for the Setup tag would look like:

<script>
  (function() {
    var el = document.createElement('script');
	el.src = 'https://code.jquery.com/jquery-3.2.1.js';
	el.async = 'true';
	el.addEventListener('load', function() {
	  window.google_tag_manager[{{Container ID}}].onHtmlSuccess({{HTML ID}});
    });
    document.head.appendChild(el);
  })();
</script>

Note that for this to work, you must enable the Built-in variables Container ID and HTML ID.

The mysterious window.google_tag_manager[].onHtmlSuccess() method is an internal function you need to use in tag sequencing if you want GTM to wait for some code (e.g. asynchronous callbacks) to execute before moving from the setup tag to the main tag. If you didn’t have this piece of code here, GTM would simply jump straigth to the main tag after executing the last line of the setup tag, and in that case it’s very possible that jQuery hasn’t completely downloaded yet.

Summary

These two methods can be used to get the best of both worlds: asynchronous requests WITH predictability. Race conditions can be brutal and difficult to identify. It’s only once you start logging JavaScript errors that you might notice an increase in error messages like jQuery is not defined. This is a signal that you might be trying to use a resource before it has completely loaded. Using a load listener is a handy way to combat this.