Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google Analytics hitCallback with timeout

I was checking out Google Analytics Guides and saw this code:

// Gets a reference to the form element, assuming
// it contains the id attribute "signup-form".
var form = document.getElementById('signup-form');

// Adds a listener for the "submit" event.
form.addEventListener('submit', function(event) {

  // Prevents the browser from submiting the form
  // and thus unloading the current page.
  event.preventDefault();

  // Sends the event to Google Analytics and
  // resubmits the form once the hit is done.
  ga('send', 'event', 'Signup Form', 'submit', {
    hitCallback: function() {
      form.submit();
    }
  });
});

Then it says "If (for whatever reason) the analytics.js library fails to load, the hitCallback function will never run." And it provide a solution as below:

// Gets a reference to the form element, assuming
// it contains the id attribute "signup-form".
var form = document.getElementById('signup-form');

// Adds a listener for the "submit" event.
form.addEventListener('submit', function(event) {

  // Prevents the browser from submiting the form
  // and thus unloading the current page.
  event.preventDefault();

  // Creates a timeout to call `submitForm` after one second.
  setTimeout(submitForm, 1000);

  // Keeps track of whether or not the form has been submitted.
  // This prevents the form from being submitted twice in cases
  // where `hitCallback` fires normally.
  var formSubmitted = false;

  function submitForm() {
    if (!formSubmitted) {
      formSubmitted = true;
      form.submit();
    }
  }

  // Sends the event to Google Analytics and
  // resubmits the form once the hit is done.
  ga('send', 'event', 'Signup Form', 'submit', {
    hitCallback: submitForm
  });
});

I can understand the code above. But what follows next confused me:

function createFunctionWithTimeout(callback, opt_timeout) {
  var called = false;
  function fn() {
    if (!called) {
      called = true;
      callback();
    }
  }
  setTimeout(fn, opt_timeout || 1000);
  return fn;
}

// Gets a reference to the form element, assuming
// it contains the id attribute "signup-form".
var form = document.getElementById('signup-form');

// Adds a listener for the "submit" event.
form.addEventListener('submit', function(event) {

  // Prevents the browser from submiting the form
  // and thus unloading the current page.
  event.preventDefault();

  // Sends the event to Google Analytics and
  // resubmits the form once the hit is done.
  ga('send', 'event', 'Signup Form', 'submit', {
    hitCallback: createFunctionWithTimeout(function() {
      form.submit();
    })
  });
});

My understanding is, in that last block of code, if the analytics.js library fails to load, the hitCallback function will not run, which means the setTimeout() will not run. So the last block of code seems not having the functionality as the second block of code.

Did I miss anything? Or this is what it is?

like image 819
Brody Chen Avatar asked Mar 28 '26 02:03

Brody Chen


1 Answers

I think what needs clearing up first here is what Google means when they say hitCallback fails. This snippet from their documentation on the property sheds a little light on that:

You may want to avoid using hitcallBack to execute code that is critical to your application since it's possible it may not get called in rare cases (e.g. if the server doesn't respond or analytics.js fails to load). In this case you can set a timeout to ensure execution.

After reading the question initially, I thought if the ga object sends a hit then surely hitCallback has to work, that this scenario had to be all or nothing. From the sound of it, Google is only warning about rare blips in the system or obscure load failures that occur between the hit send, and the callback functionality. What I'm understanding from this statement is that the ga object is always going to employ hitCallback, but there's a small chance that a response will never come back from the server. It's because of these scenarios that it's recommended not to have mission-critical code depending on this property.

To explain the code above, the setups in blocks 2 and 3 accomplish the same goal with different levels of abstraction. In the 2nd block, we have a function:

function submitForm() {
    if (!formSubmitted) {
      formSubmitted = true;
      form.submit();
    }
  }

that, at the time the form's submit event is fired, is called once in a setTimeout and once in the hitCallback. So if the latter never gets a response, the 1-second delay on the former will submit the form without depending on a response.

The 3rd block wraps the setTimeout functionality in a function that is run when the javascript is first loaded:

function createFunctionWithTimeout(callback, opt_timeout) {
  var called = false;
  function fn() {
    if (!called) {
      called = true;
      callback();
    }
  }
  setTimeout(fn, opt_timeout || 1000);
  return fn;
}

In this case, createFunctionWithTimeout is calculated at the time the javascript is read, and the function that is passed in will either be called by hitCallback upon returning from analytics, or be called by the setTimeout from before.

Since hitCallback works as a callback, we can verify this by using event listener callbacks as a test. Take this code for example:

// Same function as above
function createFunctionWithTimeout(callback, opt_timeout) {
  var called = false;
  function fn() {
    if (!called) {
      called = true;
      callback();
    }
  }
  setTimeout(fn, opt_timeout || 1000);
  return fn;
}

// The createFunctionWithTimeout will be calculated initially when it loads
// If you don't press the button for 2 seconds, the alert will still pop up
// If you press the button before 2 seconds passes, you'll see the popup earlier
$('#btn').click(createFunctionWithTimeout(function() { 
	alert("Hello!"); 
  }, 2000)
);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<input type='button' id='btn' value='test' />

So whether you go with the 2nd or 3rd block depends on how much you want to abstract the setTimeout functionality. Google's documentation recommends the 3rd case if you plan to use the function a lot as this cuts down on duplicating the code, but ultimately, both accomplish the goal.

like image 51
Jonathan Michalik Avatar answered Mar 29 '26 17:03

Jonathan Michalik