Sunday, January 20, 2013

Handling asynchronicity in an API

Designing a good API is one of the more difficult tasks when it comes to software development. Unfortunately it is also one of the important tasks, since it is really hard to change an API after it's been made public. Well OK, maybe it is not hard for you to change the API, but it is hard on the users of your API: they have to update all their client programs if you decide to make changes.

Nowadays many APIs deal with asynchronous operations, especially when an API bridges the gap between a client-side web application and a server-side back-end. Whenever a web application needs some additional data from the server, it needs to initiate a (XMLHttpRequest) call to the server. And since such a call can take a significant amount of time it should (in almost all cases) be handled asynchronously.

Checking if the title is loaded

Here is how I recently saw this done in a JavaScript API:

    function displayTitle(object) {
      if (object.isLoaded()) {
        document.getElementById('title').innerText = object.getTitle();
      } else {
        function onLoaded(object) {
          object.removeEventHandler('load', onLoaded);
          document.getElementById('title').innerText = object.getTitle();
        }
        object.addEventHandler('load', onLoaded);
        object.load();
      }
    }

To display the title of the object, we first have to check if the data for the object has been loaded from the server. If so, we display the title straight away. If not, we load it and then display the title.

That is quite a lot of code, for what is a quite common operation. No wonder so many people dislike asynchronous operations!

Use a callback for the asynchronous operation

Let's see if we can simplify the code a bit.

For example, the event handler is only used once here. And although registered event handlers can be useful for things that happen all the time, clearly in many cases people will want to check if the object is loaded in a regular sequence of code - not respond to whenever the object is (re)loaded.

If we allow the onLoaded function to be passed into the load() call, things clean up substantially:

    function displayTitle(object) {
      if (object.isLoaded()) {
        document.getElementById('title').innerText = object.getTitle();
      } else {
        object.load(function onLoaded(object) {
          document.getElementById('title').innerText = object.getTitle();
        });
      }
    }

The nice thing about this change it that you can add it to the existing API after releasing it:

    MyObject.prototype.loadAndThen = function(callback) {
      function onLoaded(object) {
        object.removeEventHandler('load', onLoaded);
        callback(object);
      }
      this.addEventHandler('load', onLoaded);
      this.load();      
    };

This is mostly a copy of the code we removed between the first and second fragments above. But now instead of everyone having to write/copy this plumbing, you just have to write it once: in the prototype used for the object in question.

Note that I named the function loadAndThen, to avoid conflicting with the existing load function.

Assume asynchronicity

But when I started using the Firebase API a while ago, I noticed how natural their way of handling asynchronous operations feels. If we'd apply their API style to the above example, the displayTitle function would become:

    function displayTitle(object) {
      object.getTitle(function (title) {
        document.getElementById('title').innerText = title;
      });
    }

Since the title might have to be loaded from the server, they require you to always pass in a callback function. And they will simply call that function once the title is loaded.

Now I can see you thinking: "but what happens if the title is already loaded?" That is the beauty of it: if the title is already loaded, they simply invoke the callback straight away.

If we'd like to implement such an API on top of our example, we could implement getTitle like this:

    MyObject.prototype.getTitleAndThen = function(callback) {
      if (this.isLoaded()) {
        callback(this.getTitle());
      } else {
        function onLoaded(object) {
          object.removeEventHandler('load', onLoaded);
          callback(object.getTitle());
        }
        this.addEventHandler('load', onLoaded);
        this.load();      
    };

Like before I gave the function a suffixed name to prevent clashing with the existing getTitle function. But of course if you end up implementing this in your own API, you can just stuff such code in the regular getTitle function (which probably reads the title from a member field of this).

If you think this is a lot of code to add to your framework, look back at our first example. If you don't add the code to your framework, every user will end up adding something similar to their application.

Conclusion

By assuming that certain operations are (or at least can be) asynchronous, you can reduce this code:

    function displayTitle(object) {
      if (object.isLoaded()) {
        document.getElementById('title').innerText = object.getTitle();
      } else {
        function onLoaded(object) {
          object.removeEventHandler('load', onLoaded);
          document.getElementById('title').innerText = object.getTitle();
        }
        object.addEventHandler('load', onLoaded);
        object.load();
      }
    }

To this:

    function displayTitle(object) {
      object.getTitle(function (title) {
        document.getElementById('title').innerText = title;
      });
    }

The biggest disadvantage I see in the second example is that users of your API are more directly confronted with closures. Although I hang around on StackOverflow enough to realize that closures are a real problem for those new to JavaScript, I'm afraid it is for now a bridge that everyone will have to cross at their own pace.

No comments: