Mar 19, 2011

A enhanced curry method

JavaScript is a functional language, it supports function composition, we can do curry with JavaScript. Douglas Crockford has defined "curry" method in his book JavaScript: The Good Parts to facilitate the steps to define curried function.

Function.prototype.method = function ( name, func ) {
 if ( !this.prototype[name] ) {
  this.prototype[name] = func;
 }
};

Function.method( 'curry', function () {
 var slice = Array.prototype.slice,
   args = slice.apply( arguments ),
   that = this;
 return function () {
  return that.apply( null, args.concat( slice.apply( arguments ) ) );
 };
} );

//so I can write the following code

test( "test curry", function () {
 function cancat_abc( a, b, c ) {
  return a + b + c ;
 }

 var cancatBc = cancatAbc.curry( "a" );
 var result = cancatBc( "b", "c" );
 equal( result, "abc", "the first parameter is preloaded" );

} );


Nice. But there is problem here, the preloaded arguments have to be the first consecutive arguments. So if I have want to preload the second and third parameter. The curry method does not support that.


Of course, I can do it manually instead of using the curry method, but this is not quite DRY (Don't Repeat Yourself). So I modify the curry method as follow to do the work.


Function._ = {};

Function.method( 'curry', function () {
 var slice = Array.prototype.slice,
   preloads = slice.apply( arguments ),
   _ = Function._,
   fn = this;
 return function () {
  var args = slice.apply( arguments );
  for ( var i = 0; i < preloads.length; i++ ) {
   if ( preloads[i] == _ ) {
    preloads[i] = args.shift();
   }
  }

  return fn.apply( null, preloads.concat( args ) );
 };
} );

var _ = Function._;

test( "test curry", function () {
 ok( _, "an dummy object has been defined" );
 function cancat_abcd( a, b, c, d ) {
  return a + b + c + d;
 }

 var cancatBd = cancatAbc.curry( "a", _, "c", _ );
 var result = cancatBd( "b", "d" );
 equal( result, "abcd", "curry should work in order" );

} );

the when method

In jQuery 1.5, there is a "when" method.

function when(objects) {}

//we can call with like
jQuery.when(object1, object2, object3, ..).then(done, fail);


The semantics of the function is when ALL the objects'(object1, object2, ..) value is realized, then call the done function with all the realized value, when one of them fail, call the fail function. These objects can be a "realized" value or a promise (promising value). If all the value are already "realized" initially, the done function will be called synchronously.

the promise object

The jQuery.Deferred object has a function .promise(), it returns a promise object. Some other people call it future. In the async framework of .net, it is called Task<T> . It an object that promise to deliver a value in the future. So if believe that promise, you can express your believing like this


promise.done(function () {
  var valuesOfFuture = [].slice.call(arguments, 0);
   alert();
});

The promise object does not has method of "resolve" and "reject". Because these method belong to server side. This pattern is very popular, but jQuery stream line this pattern. It is very useful pattern.

jQuery.Deferred Object

Internally, the Deferred object is implemented with two _deferred object. The Deferred object offer two set of functions, done/resolveWtih/resolve/isResolved and fail/rejectWith/reject/isRejected. The are basically the functions of two internal _deferred object. The failed set function is the done/resolveWith/resoved/isREsolved function of fail object. But we don't have cancel function, because it is unnecessary, it is replaced with reject function. The done function will empty the failed list, and the reject function will empty the done list.


It also introduced two new functions, then and promise. The then function is short cut to push a done function and failed function at one shot. We care about the success and compensation at the same time, we should consistently use then function instead of interweaving done or fail method. But if we don't care about that, we can interweave done and fail method. However, I think the then method should always be used, as it adds very little cost.


Another new method is promise method. It basically return a new object or decorate an existing object with "promise prospect" or promise methods then, done, fail, isResolved, isRejected, promise. The promise object does the same thing as Deferred object, except it can not resolve, reject, which should be used at the server side. When we first call the d.promise(obj) with a real obj, then the obj is augmented with the promise methods and cached internally, and this pattern should be used at the first time. The subsequent call d.promise() will return the cached object.


The constructor Deferred(initialize) can be passed with an initialize function. Your function can be defined as


function (deferred) {
  //this == deferred
   this.then(x, y);
   this.m1 = "m1";
}

jQuery._Deferred Object

The jQuery._Deferred is an object maintaining a task list. To understanding it, help you to understand how the jQuery.Deferred and jQuery.when works. The _Deferred object has the following use case.


  • Push your task into the list to be run(resolved) in the future. It has been resolved, call the resolved method again.

  • Resolve(run) the tasks in the list, the task will be run in the order you push it, [].shift method is used to dequeue from the list. If everything runs fine, the task will be empty.

  • Push the task into task list during the task is run. But the new task will the last one to run.

  • Exception handling inside of your task. If the current task is run synchronously, you can optionally use exception handling, an exception is not caught, the remaining tasks will be canceled.

  • You have an option to eagerly cancel the remaining task, in the current running task. This is an advanced task.

  • Once you resolve the task list one a set of values, then the resolve context, and values are cached. If you resolve it again with new values, the new task still be run, but run in the old context, and values.