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.

1 comment:

BabyPlaza XeNgua said...

Vì sao lại có quan ngại “thuốc Fucoidan có tốt không” ?
Như kể trên, chúng ta thấy Fucoidan có rất nhiều tác dụng tốt. Tác dụng của thuốc Best Fucoidan http://nammongtay.com/tags/thuoc-chong-ung-thu-fucoidan .Với nguồn gốc tự nhiên, chắc chắn giá thành của nó sẽ cao hơn các loại thuốc hóa học khác. Chính bởi lý do này đã khiến những người xấu lợi dụng để trục lợi cho bản thân. Chữa ung thư phổi với thuốc Fucoidan http://thuocbomat.net/tag/thuoc-best-fucoidan-70.Họ tận dụng đặc điểm tốt cho sức khỏe, được nhiều người sử dụng của Fucoidan, sau đó làm giả Fucoidan chất lượng kém, số tiền bỏ ra để chế xuất rất nhỏ và bán ra với giá thành bằng giá của Fucoidan thật trên thị trường. Đánh giá thuốc Fucoidan http://thuocgiamcannhanhantoan.com/tag/thuoc-fucoidan-chua-ung-thu-phoi. Nhiều người tiêu dùng do quá nhẹ dạ cả tin đã mua phải loại Fucoidan giả, nên khi sử dụng sẽ không thấy được những tác dụng tốt của nó. Thuốc chống ung thư fucoidan http://thuocgiaidocgan.net/tag/danh-gia-fucoidan . Thậm chí, một số loại Fucoidan giả còn đem đến ảnh hưởng xấu cho sức khỏe. Đây chính là lý do lý giải vì sao lại có thắc mắc về chất lượng của Fucoidan. Một lời khuyên dành cho người tiêu dùng là nên tìm các cơ sở kinh doanh uy tín để mua loại thuốc bổ dưỡng này.