Friday, November 8, 2013

jQuery Deferred Object - Creating Your Own

In my last post, jQuery Deferred Object - Your New Best Friend, I gave a quick overview of what a deferred object is and how to use it. This time let's go a bit deeper and explore creating your own deferred object for a much more practical use - asynchronous downloading and processing of audio files.

One of my favorite web sites is HTML5Rocks. They have all of the latest information on developing cutting edge HTML5 web apps. As I continue building my HTML5 game, I've turned to them for more information on several topics including the Web Audio API. In one of their articles,  Getting Started with Web Audio API, they create a BufferLoader class.


I thought it would be fun to convert this class to use a deferred object and streamline it even more by getting rid of the object constructor and instead use a closure. So let's get started.

Getting Rid of the Constructor
The BufferLoader uses a function as an object creator. There is nothing wrong with that per se, it is just a pattern which I rarely use these days. Instead I normally prefer to use a closure. Think about why they are creating an object. Since both the loading and the converting of the files are asynchronous operations they want to be to support multiple calls into the same code. Using a constructor will guarantee that each instance has access to its own set of variables and won't interfere with any other call. A closure does the same thing, but more succinctly. So we will delete the constructor function. We also will wrap our entire code in a function to isolate from any other code in the browser. We use a single global object, RocknCoder, to hold all of our global stuff. We make the loadBuffer() method an internal method of the new RocknCoder.loadAudioFiles() method. This also makes it accessible globally.

Creating the Closure
Most of the instance variables are moved or passed into the loadAudioFiles() method. The this variable or its alternate name, loader, is no longer necessary, so we delete it to. All of its uses become the name without the "loader.", for example "loader.context" becomes simply "context". I don't know about you, but too much using of "this" in JavaScript, gets really confusing. To create the closure we call the loadBuffer method. When this method is called, it essentially creates a snapshot of the environment, all of the variables and their values become frozen, so that when an async callback happens, the state of the variables is restored. Note the subtle change of the parameters passed to loadBuffer(). Previously it was passed a single URL and index which was to the bufferList[] array. Now it is passed all of the URLs as an array and an index value which indicates which URL is currently being loaded. Once a URL is loaded and processed, index is incremented and if we haven't loaded all of the URLs yet, loadBuffer() is called recursively. Do it this way also eliminates the need for load() function, so we delete it too.

Making Use of the Deferred Object
Right off the back in the loadAudioFile() method, we create our deferred object by calling the jQuery $.Deferred() method. The last line of the method returns the deferred object, myDeferred to the caller. Keep mind that this method, will continue to run even after it returns to the caller since their are multiple asynchronous callback going on here. In the method we curiously use the XMLHttpRequest object instead of using jQuery's version of it. This is mainly because the XMLHttpRequest object supports the "arraybuffer" type which allows us to load binary data, very important for audio.

If we encounter an error along the way we can call the reject() method and pass some error information all with it. This will cause our deferred object to fail and the information passed to it will be available to the fail() method.

Once all of our audio files has loaded and been processed, we simply resolve() our deferred object. In this case we pass our bufferList, which contains all of our processed audio files to our deferred object. This will make them available to our done() method. Also note how we eliminated the need to track the index count on bufferList by using the push() method instead an index.

We could have also used the deferred object's progress() method each time we successfully finished either loading or processing a file. That would allow the caller to update a UI element to keep the user informed as to our progress. Oh well, maybe next time.

Summary
We started with roughly 46 lines of code in three methods and converted into roughly 42 lines in one method. Doesn't seem like too great of a win but the code is cleaner to read and most of the win is for the caller. Instead of having to deal with a single callback, where they have to sort out the results. They now get separate done() and fail() methods. Plus the deferred object passed here can be combined with others in a when() statement. An example of this is in the game I am building. We wait for all of the audio files, the sprite map, and a minimum of three seconds to pass before we leave the splash screen. An example of which is below.

If you would like to see more of the game, be sure to check out my repo on GitHub at: https://github.com/Rockncoder/planegame. Be sure to check out the first post of this series: jQuery Deferred Object - Your New Best Friend.