Working with asynchronously loaded javascript

September 1, 2010 Pivotal Labs

My pair and I were recently working on an app that asynchronously loaded the Facebook Javascript SDK but synchronously loaded the jQuery library. We had to invoke some javascript once Facebook and jQuery were both loaded, and this post describes how we did it.

First, we followed Facebook’s recommendation and asynchronously loaded the Facebook SDK right after the start of the body tag. Then for other reasons we put all other javascript includes right before the end of the body tag. The resulting html looked like this:

<html>
  <head>
  </head>
  <body>
    <div id="fb-root"></div>
    <script>
      (function() {
        var e = document.createElement('script');
        e.src = document.location.protocol + '//connect.facebook.net/en_US/all.js';
        e.async = true;
        document.getElementById('fb-root').appendChild(e);
      }());
    </script>

    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  </body>
</html>

When the Facebook js loads, it looks for a function named fbAsyncInit and invokes it if it’s there. When jQuery detects that the document has loaded, it triggers a document ready event. Since the Facebook js is being loaded asynchronously, though, the sequence of these events is unpredictable. Things could happen in one of 2 ways:

  1. fbAsyncInit is called then $(document).ready() is triggered
  2. $(document).ready() is triggered then fbAsyncInit is called

We needed to run code once Facebook and jQuery were both loaded. If Facebook loaded first, this code worked:

$(document).ready(function(){
  $("#content").html("Welcome!")
  FB.init({
    appId  : 'YOUR APP ID',
    status : true,
    cookie : true,
    xfbml  : true
  });
});

However, if Facebook hadn’t loaded, it blew up on FB being undefined.

If jQuery loaded first, then this code worked:

window.fbAsyncInit = function() {
  $("#content").html("Welcome!")
  FB.init({
    appId  : 'YOUR APP ID',
    status : true,
    cookie : true,
    xfbml  : true
  });
}

However, if jQuery hadn’t loaded, it blew up on $ being undefined.

After a bit of head scratching we came up with this:

$(document).ready(function(){
  function init(){
    $("#content").html("Welcome!")
    FB.init({
      appId  : 'YOUR APP ID',
      status : true,
      cookie : true,
      xfbml  : true
    });
  }

  if(window.FB) {
    init();
  } else {
    window.fbAsyncInit = init;
  }
});

On document ready, if Facebook is loaded, invoke the function immediately. If Facebook is not loaded, define the fbAsyncInit function, which will be called when Facebook loads.

Going one step further, we moved the page-specific javascript into an event listener, then triggered a custom event that page-specific js could listen for:

$(document).ready(function(){
  function facebookReady(){
    FB.init({
      appId  : 'YOUR APP ID',
      status : true,
      cookie : true,
      xfbml  : true
    });
    $(document).trigger("facebook:ready");
  }

  if(window.FB) {
    facebookReady();
  } else {
    window.fbAsyncInit = facebookReady;
  }
});

// elsewhere in the code
$(document).live("facebook:ready", function(){
  $("#content").html("Welcome!")
});

Thanks to Evan Farrar and Rachel Heaton for making this work!

About the Author

Biography

Previous
Pivotal Tracker GoGaRuCo Haiku Contest!
Pivotal Tracker GoGaRuCo Haiku Contest!

Missed your chance to sign up for GoGaRuCo? They just sold the last ticket over the weekend. It's looking l...

Next
Cucumber performance improvements with Devise Token Authenticatable
Cucumber performance improvements with Devise Token Authenticatable

We're using Devise's token_authenticatable to improve our Cucumber test suite performance. Here's an examp...