Partager via


Fun Friends For Functions

And now for a more techy post. In ECMAScript, functions are considered first-class objects. "What," you might ask, "differentiates a 'first-class' object from a merely 'economy-class' object?" Well, the main thing is that functions in ECMAScript are treated just like any other type of data (strings, numbers, arrays, etc.) in that they can be assigned to variables, they can be passed as function arguments, and they can have properties and methods of their own. They also have a prototype object, just like all other ECMAScript objects. The upshot of this is that you can do fun things with functions in ECMAScript that you can't do in other languages such as C or Java.

In this entry I will introduce four types of "helper" functions that are ideally suited to HDi development. They are:

  • Binding a function to a set of arguments and / or a 'this' object
  • Wrapping an existing function in an exception handler
  • Chaining function calls together one after the other
  • Calling a function after a specified period of time

In all cases, you write the actual function (the one that does the interesting work) just as you normally would, but when it comes time to call that function you do something funky with it to make some magic happen. Exactly why you might want to do this will become clearer soon, but it mostly comes down to how you write event handlers and callbacks.

A common theme in this post will be that you want to avoid closures as much as possible, and using these helpers should get you a long way to doing that (even though these helper functions rely on closures, they do so in a "safe" manner). And although there are various ad-hoc ways to accomplish the same tasks, generally these require writing specialised wrapper functions or following ugly coding practices (which I might blog about in the future) when what I would prefer to focus on is writing highly readable, highly re-usable code and utilising some clever, general-purpose helper functions to take care of all the tricky dirty work.

Binding to arguments

Many operations in HDi use a callback function to notify you when a specific task is completed (such as a File I/O operation or an event handler). Oftentimes you will want to associate more information with your callback than the HDi API actually allows for, and there are a couple of ways to accomplish this. But first, an example:

Say you have an application where you want to dynamically download some images from the network and display them inside a markup page. The basic code you want to write is probably like this (the code is simplified for clarity; obviously there is some additional code to convert the onStateChange callback into a single "the download is complete" call, etc.):

 

// function to download an image
function DownloadImage(src, dest)
{
var httpClient = Network.createHttpClient(src, dest);
httpClient.onStateChange = SetBackgroundImage;
httpClient.send();
}

// function to set a backgroundImage
function SetBackgroundImage(markupId, url)
{
document[markupId].style.backgroundImage = url;
}

// call the download function... but what next?
DownloadImage("https://foo/bar.png", "file:///filecache/bar.png");

 

Note that both functions have been written to be "highly readable, highly re-usable" and do one thing in a very straight-forward manner. The problem, of course, is that this code won't work because there is no way for the callback to SetBackgroundImage to communicate what markupId or the final download url is supposed to be.

One common way of solving this problem is to use a nested function, like this:

 

// function to download an image and set it as the background URL
function DownloadBackgroundImage(src, dest, markupId)
{
var httpClient = Network.createHttpClient(src, dest);
httpClient.onStateChange = SetBackgroundImage;
httpClient.send();

  // nested function to set a backgroundImage
function SetBackgroundImage()
{
document[markupId].style.backgroundImage = dest;

    // Clean up the memory
httpClient = null;
}
}

// call the download function and update the element 'button1'
DownloadBackgroundImage("https://foo/bar.png", "file:///filecache/bar.png", "button1");

 

Note that even though we've done work to ensure the closure won't memory leak memory, there is still at big problem in that neither the download function nor the background setting function are generic any more. If you want to (eg) download an XML file, or if you want to set the backgroundImage to a file on persistent storage, you will need to either add some more hacks to this function (making it less readable and less maintainable) or you will need to duplicate the code into a similar-but-not-entirely-the-same function.

The solution I prefer is to use the fact that functions are first-class objects and solve this problem using "argument binding." In this case, the code would be re-written as follows:

 

// function to download a file and then call 'handler' when it is done
function DownloadFile(src, dest, handler)
{
var httpClient = Network.createHttpClient(src, dest);
httpClient.onStateChange = handler;
httpClient.send();
}

// function to set a backgroundImage
function SetBackgroundImage(markupId, url)
{
document[markupId].style.backgroundImage = url;
}

// Call the download function, passing in the set-image function as the callback handler
var setBackgroundHandler = SetBackgroundImage.BindToArguments("button1", "file:///filecache/bar.png");
DownloadFile("https://foo/bar.png", "file:///filecache/bar.png", setBackgroundHandler);

 

Now both the download function and the set-image function are still written in a clear, simple, re-usable fashion, but we can easily chain them together to get the desired result. Even the call that makes this possible (the BindToArguments call) is straight-forward and understandable. All that is missing is the magic BindToArguments helper function... which looks like this:

 

// helper function to return a copy of the target function, but
// bound to the original arguments
function Function_BindToArguments()
{
// store a reference to the target function and its arguments
var storedFunction = this;
var storedArguments = arguments;

  // return the nested function
return boundFunction;

  // nested function simply invokes the stashed values
function boundFunction()
{
storedFunction.apply(null, storedArguments);
}
}

// make the helper available to all functions
Function.prototype.BindToArguments = Function_BindToArguments;

 

As noted in the comments, this helper function simply stores a reference to the "target" function and the arguments that are passed to the BindToArguments call. Then it returns a nested function, which when called will invoke the stored function (SetBackgroundImage) with the stored arguments ( "button1" and "file:///filecache/bar.png" ). Note that the apply function is a built-in function of ECMAScript Function objects that invokes a given function with the specified 'this' object and arguments array.

This is the most basic form of a "binding" helper, and the resultant function is one that has no 'this' object and that cannot take any arguments of its own. It also can't participate in a typical logging infrastructure because much of the required metadata (original function name, number of expected arguments, etc.) is missing. The good news is that all this can be added with the help of more code, and hopefully that's something I'll be posting soon.

As usual, this is longer (and took longer) to write, so I'll have to save the other "friends" for later. The depressing thing is that actually writing the code is the easy part (and it's all done already); it's wrapping it all up in a post and explaining how things work that take forever!