Saturday, July 21, 2012

Asynchronous loading Facebook Connect...

One of the issues you might encounter in using Facebook Connect is simply the load times for this file. After all, how long does it really take to load the all.js file hosted on their CDN servers?
<script type="text/javascript" src="https://connect.facebook.net/en_US/all.js"></script>
The Facebook docs suggest the following approach for loading all.js, which leverages the HTML5 async attribute to allow other JavaScript code to be loaded and executed in parallel.  (Also notice that the fb-root <div> element is inserted before any Facebook Connect JavaScript code is executed, mostly because the code depends on the presence of such a DOM element to insert its cross-domain handling code.)
<div id="fb-root"></div>
<script>
  window.fbAsyncInit = function() {
    FB.init({
      appId      : 'YOUR_APP_ID', // App ID
      channelUrl : '//WWW.YOUR_DOMAIN.COM/channel.html', // Channel File
      status     : true, // check login status
      cookie     : true, // enable cookies to allow the server to access the session
      xfbml      : true  // parse XFBML
    });

    // Additional initialization code here
  };

  // Load the SDK Asynchronously
  (function(d){
     var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
     if (d.getElementById(id)) {return;}
     js = d.createElement('script'); js.id = id; js.async = true;
     js.src = "//connect.facebook.net/en_US/all.js";
     ref.parentNode.insertBefore(js, ref);
   }(document));
</script>
The proposed solution in the Facebook docs work fine, but what if you have cases in your pages that depend on the use of the FB object (i.e. to check login status of a user)?  The fbAsyncInit() function is called within the Facebook Connect code after everything is setup and calls made within this function will guarantee the existence of the FB object.   If you have other JavaScript code executed in different parts of your app, you'd have to override the window.fbAsyncInit and possibly duplicate a lot of the initialization code, since there is no way to bind multiple handlers to the same event.

In addition, you might also think that putting code within the $(document).ready() handler would provide adequate time for the all.js to be fully loaded, but the browser can signal that the DOM has fully been loaded before the JavaScript itself has completely executed.  In other words, specifying JavaScript <script> tags at the top of your HTML document doesn't guarantee that the JavaScript will be executed before the rest of the DOM has been parsed by the browser.   The net effect is that you can have race conditions, in which the browser throws "'FB' is undefined" errors when you attempt to make Facebook Connect loaded.

One phenomenon I noticed is that jQuery could be consistently loaded before the fbAsyncInit() function was ever called.   This might best be explained by peeking at the internals of the all.js file, since the code is currently structured to execute 6910 lines before the window.FB object is even available. In addition, a lot of internal JavaScript objects are created too and making FB.init() calls often result in network calls to Facebook's OAuth servers to determine login state.  The implication is that we could safely assume that jQuery library was available to us and allow us to create custom events with multiple handlers:

window.fbAsyncInit = function() {
    FB.init({apiKey: [YOUR FACEBOOK APP ID]
             oauth: true,
             channelUrl : [CHANNEL_URL]
             cookie: true
            });

    if (jQuery) {
        jQuery(document).trigger('fbAsyncInit');
    }
   };

In other parts of code, we simply create custom bind handlers and attach Facebook-specific code:

   $(document).bind('fbAsyncInit', function () {
     FB.getLoginStatus(function (response) { });
   });

By using this approach, we've been able to sidestep these FB undefined errors we observed and allow different parts of our application to run FB-specific code. Facebook should probably allow multiple post-init handlers, but in the interim, you may need to adopt a similar approach to avoid such load race conditions.

No comments:

Post a Comment