Archive for June, 2009

Making JSON-P web API calls more robust

Wednesday, June 17th, 2009

Recently I had a discussion with some online colleagues about JSON-P web services.  There was a post on Ajaxian about how Microsoft Bing wraps their JSON-P results in a try..catch block.  I thought this was a neat little addition to JSON-P, which I’ve felt for a long time is an extremely interesting little hack.

Now, the point I made was that while the try…catch thing was a nice addition, it of course didn’t address what I see as the biggest problem with JSON-P, and that’s having no way to really know if the request completed or not.  We tossed around some ideas and a couple of us pretty quickly and independently come up with the idea of having a concurrent timeout with the in-flight request.

Before I go any further, I’ll qualify this all by saying I strongly suspect this isn’t an original idea, but I do know that, at least in my case (I can’t say what goes on in the head of others, I can barely make sense of what goes on in mind!) that I thought it up on my own.

Be that all as it may, the point is that it’s a viable approach, but it needed some refinement.  So, I threw together some code today.  First, a JavaScript file:

/**
 * JSONP makes JSON-P requests and do so with a timeout mechanism so you’ll
 * always know whether the request was successful or not.
 */

var JSONP = {

  /* Amount of time in seconds before a request is considered timed out. */
  timeoutSeconds : 5,

  /* Reference to the document’s head tag. */
  headTag : document.getElementsByTagName(“head”).item(0),

  /* Collection of objects, one for each in-flight request. */
  requestObjects : { },

  /**
   * Call this method to fire off a JSON-P request.
   *
   * @param inConfig An object containing whatever parameters are needed to
   * make the remote call.  The attributes of this object are used to construct
   * a query string.
   */

  request : function(inConfig) {

    // Create unique ID for request.
    var requestID = “req_” + new Date().getTime();

    // Create request object and populate with internal data.
    var requestObject = { };
    requestObject.requestID = requestID;
    requestObject.callback = function(inResponse) {
      JSONP.callback(requestID, inResponse);
    };
    requestObject.realCallback = inConfig.callback;
    requestObject.onTimeout = inConfig.onTimeout;
    inConfig.callback = “JSONP.requestObjects.” + requestID + “.callback”;

    // Add query string to URL.
    if (inConfig.url.charAt(inConfig.url.length1) != “?”) {
      inConfig.url += “?”;
    }
    var queryString = “”;
    for (var attribute in inConfig) {
      if (queryString != “”) {
        queryString += “&”;
      }
      queryString += attribute + “=” + inConfig[attribute];
    }
    inConfig.url += queryString;
    requestObject.inConfig = inConfig;

    // Now create the script tag for this request.
    var scriptTag = document.createElement(“script”);
    requestObject.scriptTag = scriptTag;
    scriptTag.setAttribute(“src”, inConfig.url);
    scriptTag.setAttribute(“type”, “text/javascript”);

    // Now create the timeout.
    requestObject.timeout = setTimeout(function() {
      JSONP.timeoutElapsed(requestID);
    }, this.timeoutSeconds * 1000);

    // Kick off the request.
    this.headTag.appendChild(scriptTag);

    // Finally, put the requestObject in the collection.
    this.requestObjects[requestID] = requestObject;

  },

  /**
   * Internal intermediary callback that the JSON-P request calls.
   *
   * @param inRequestID The ID associated with the requestObject.
   * @param inResponse  The response from the remote server.
   */

  callback : function(inRequestID, inResponse) {

    // Get the request object associated with this request.
    var requestObject = JSONP.requestObjects[inRequestID];

    // Might not have a reqested object, if the request comes back after the
    // timeout period.
    if (requestObject) {
      // Clear the timeout so the request doesn’t time out.
      clearTimeout(requestObject.timeout);
      // Call the specified callback.
      requestObject.realCallback(inResponse);
      // Delete the request object.
      delete JSONP.requestObjects[inRequestID];
    }

  },

  /**
   * This is called when a request timeout occurs.
   *
   * @param inRequestID The ID associated with the requestObject.
   */

  timeoutElapsed : function(inRequestID) {

    // Get the request object associated with this request.
    var requestObject = JSONP.requestObjects[inRequestID];

    // There should never be a case where there is no requestObject here,
    // but we’ll check for it anyway, just in case I missed something.
    if (requestObject) {
      // Copy pertinent attributes of requestObject to a new object.
      var newRequestObject = { };
      newRequestObject.requestID = requestObject.requestID;
      newRequestObject.inConfig = { };
      for (var i in requestObject.inConfig) {
        newRequestObject.inConfig[i] = requestObject.inConfig[i];
      }
      // Delete the request object, but get onTimeout first.
      var onTimeout = requestObject.onTimeout;
      delete JSONP.requestObjects[inRequestID];
      // Now call the real timeout handler, if any.
      if (onTimeout) {
        onTimeout(newRequestObject);
      }
    }

  },

}; // End JSONP.
 

Now, a test HTML file to use this:

<body>

  <head>

    <script src=“jsonp.js”></script>
    <script>

      function test1() {
        var config = {
          url : “http://search.yahooapis.com/ImageSearchService/V1/imageSearch?”,
          appid : “YahooDemo”, query : “Amanda Tapping”, output : “json”,
          callback : test1Callback, onTimeout : timeoutHandler
        };
        JSONP.request(config);
      }

      function badTest() {
        var config = {
          url : “gibberish”, appid : “gibberish”, query : “gibberish”,
          output : “json”, callback : null, onTimeout : timeoutHandler
        };
        JSONP.request(config);
      }

      function timeoutHandler(inRequestObject) {
        // Iterate its attributes
        var s = “”;
        for (var i in inRequestObject) {
          s += i + ” = “ + inRequestObject[i] + \n;
        }
        for (var i in inRequestObject.inConfig) {
          s += i + ” = “ + inRequestObject.inConfig[i] + \n;
        }
        // Show the output.
        alert(“REQUEST TIMED OUT – Request Object Dump: \n\n + s);
      }

      function test1Callback(inResponse) {
        var outputString = “Total results returned: “ +
          inResponse.ResultSet.totalResultsReturned + “<br>”;
        for (var i = 0; i < inResponse.ResultSet.Result.length; i++) {
          outputString += inResponse.ResultSet.Result[i].Title + “<br>”;
        }
        document.getElementById(“divResponse”).innerHTML = outputString;
      }

    </script>

  </head>

  <body>

    <input type=“button” value=“Make Request” onClick=“test1();”>
    <br><br>
    <div id=“divResponse”
      style=“width:400px;height:250px;border:1px solid #000000;overflow:auto;”>
      Search results will appear here</div>
    <br><br>
    <input type=“button” value=“Make Bad Request” onClick=“badTest();”>

  </body>

</html>

To use it, you simply call the JSONP.request() method.  You pass to this method an object that configures the call.  Only three of the attributes, strictly-speaking, are required.

  • url – The URL of the JSON-P web service to call.  This can end with a question mark, but it doesn’t have to.
  • callback – This is the function that will be called when the response comes back.
  • onTimeout – This is the function that will be called if the request times out.

The others are completely dependant on the JSON-P services you’re calling.  Here I’m toying around with Yahoo!’s search API.  You can also set the timeout attribute on the JSONP object if you want.  This is the number of seconds to wait for a response.  If no valid response comes back in that time, then the request is considered to have timed out and your onTimeout function will be called.

Now, there’s one flaw here that I just noticed: this code assumes the name of the parameter that tells the remote service the name of the callback function is itself named callback.  This is nearly always true of JSON-P services, but it doesn’t have to be.  I’ll probably make that update at some point, but I’m sure you can handle it yourself if need be :)

So, if you couple this approach with a JSON-P service that wraps the call in try…catch, then there’s only one situation left to deal with, and that’s HTTP error codes.  During my discussion, we all concluded there appears to be no way to deal with this until everyone is implementing JSONRequest.  This timeout technique though does give you a way to deal with them indirectly.  True enough, you still won’t be able to discern a 404 from a 500, but at least you can, after some time, handle not having gotten a response and thereby avoid hanging application UIs.

But hey, as a bunch of songs have said over the years: two out of three ain’t bad :) I think these two tricks make JSON-P services a heck of a lot more robust, and that can only be a good thing for mashup builders everywhere!

Direct object attribute access versus accessor methods

Saturday, June 6th, 2009

So, I’m working on a little side project at the moment… it’s a top secret, skunkworks-type thing that you can only hear about if you sign all sorts of papers and take an unmarked jet from McCarran to Area 51 :)

Because of its nature and what it has to be capable of, I have to write my code absolutely efficiently as possible. So last night I got to wondering if there was a difference in JavaScript between direct object attribute access and using accessor methods. So, is this faster…

var a = {
  attr : “frank”
};
alert(a.attr);

…or is this faster…

var MyClass = function() {
  var attr = “frank”;
  this.getAttr = function() {
    return atttr;
  }
};

Now, logically, my expectation was that the first approach would be faster. After all, there’s less code being executed and it is, deep down in the bowels of the interpreter, a much simpler operation. But, is that expectation correct? And even if it is, how big is the difference? Also, what’s the difference across various browsers? So, I threw together some very simple test code, and here it is:

Here’s the specs of the PC I ran this on:

  • Windows XP w/sp3 and all current patches as of 6/6/2009
  • Intel Core2 Duo 6600 @ 2.4GHz
  • 3G RAM
  • No applications other than browser running in foreground and all non-critical background processes shut down

And now, the results:

Firefox 3.0.10
Iterations Direct Accessor
50000 6 42
100000 12 83
200000 24 167
400000 48 331
1000000 119 826

IE 8.0.6001.18702
Iterations Direct Accessor
50000 16 63
100000 42 113
200000 87 222
400000 175 445
1000000 434 1117

Chrome 2.0.172.30
Iterations Direct Accessor
50000 1 1
100000 1 1
200000 3 2
400000 5 5
1000000 13 12

Opera 9.64 build 10487
Iterations Direct Accessor
50000 8 19
100000 16 37
200000 34 74
400000 69 147
1000000 170 367

Safari 4 public beta 528.17
Iterations Direct Accessor
50000 0 1
100000 1 2
200000 1 4
400000 3 9
1000000 7 22

All numbers are in milliseconds and have been rounded. The first number is the number of operations tests. So, that means that the test performed 50,000 operations each of direct attribute access and access via accessor method. The direct and accessor numbers are the average number of milliseconds that number of tests took, averaged over 10 test runs. So, for example, this is telling us that on Firefox 3.0.10, it took 6 milliseconds, on average, to perform 50,000 direct attribute accesses. It’s then telling us that is took 42 milliseconds, on average, to perform 50,000 attribute accessor calls.

There’s some possibly anomalous results there, although I’m convinced they aren’t incorrect. For example, in Chrome, it seems to take less time to use accessor methods when doing 200,000 or 1,000,000 iterations. I can’t imagine why that would be, but there’s the numbers. My best guess is it’s simply a rounding error and the difference is just about nothing.

There’s also the matter of that zero for the 50,000 run of direct attribute accesses for Safari. This seems to indicate that Safari is in fact the winner in terms of raw speed for direct attribute access. That’s entirely possible I think, but again, the difference between that and Chrome for the same test is probably almost nothing and it’s simply a result of rounding.

(all of this is making me wonder why I did the rounding in the first place, but I digress)

Since no report of this nature would be complete with graphs, let’s see a couple. We’ll take each of the above data sets and show them graphically (click on each to see them full-sized)…

Now let’s see a composite of all of them together, just so we can easily compare all of them…

So, what conclusions can we draw from this? I think a couple…

  • The original expectation, that direct attribute access is faster, clearly holds. Further, in all browsers except Chrome and Safari, the progression as you increase the number of iterations is almost exactly double as the number of iterations doubles.
  • Chrome performs the best, with Safari an extremly close second (not exactly surprising). Firefox and Opera are somewhat comparable, although Firefox is definitely the winner. IE is the slowest, which I think most of us would have anticipated. Even MS’s latest and greatest is pretty significantly behind all its competitors.
  • The difference in Chrome between the two methods is virtually nill. This is frankly a big surprise to me. This is nearly true of Safari as well, but not quite.
  • Even IE, the slowest of the bunch by a country mile as they say, performs a million accessor operations in just barely over a second. Slowest for sure, but still, hey, it’s a million method calls in slightly more than a second!
  • Sort of continuing the previous point… the difference, in terms of absolute milliseconds, between the two methods for any given browser (other than Chrome and Safari, which are very close to identical) isn’t generally all that much. I purposely picked pretty large numbers in terms of the number of iterations… I mean 400,000 operations a second is pretty substantial… certainly modern JavaScript can eclipse that number, absolutely. The point though is that for many types of tasks, the difference won’t matter one bit. For example, a mouse click event handler likely won’t see any sort of real-world difference between the two methods, unless you’re doing an ungodly amount of work in response to that click, in which case your design might need some rethinking anyway :)

What implications does all of this have aside from the above points? I think two important ones. First, if you want absolutely top-notch performance you’d better stick with direct attribute access regardless of browser. I don’t think this is a surprise to anyone, me included, but it’s nice to see it proven out.

Now second though, Chrome (and Safari to a slightly lesser extent) gives us a possibility we didn’t have before and don’t really have with other browsers: the chance to write our code the “right” way with no regard to performance penalties. What I mean by the “right” way is that in many ways it’s desirable to use accessor and mutator methods on a class rather than have direct attribute access to the attributes for the same reasons we do data hiding in other languages: if the only way to mutate an attribute of an object is via some mutator method, there is (theoretically at least) more safety in that. Plus, the API is arguably more self-documenting (i.e., if there is no mutator for a given attribute then we implicitly know it’s a read-only attribute). With Chrome, and almost Safari, you can write your code that way and know it won’t kill your performance. I think this is a really big step forward for us JavaScript coders. I’d also expect this to be the case with the new JS engine in Firefox, although I didn’t pull down a nightly to try it.

(and as usual, IE is the bane of our existence, but I digress once more)

So, in the end, I don’t think I made any Earth-shattering discoveries here, mostly it just confirms what I (and probably the rest of the world) already thought was the case. Still, I think seeing some hard numbers gives a clear picture of the situation and I think this was a useful exercise in the long-run.

Comments are of course welcome…