Wednesday, January 30, 2013

Still not convinced about coffeescript?

Here is another example why you should start writing Coffeescript. The business story in this example is rather simple - I need to load all document from a collection (named Accounts), perform an asynchronous operation (notifyOwner) on each of them, and save all of them back to DB after all notifyOwner operations finish successfully. The following methods are available to achieve this simple task:
Account.find() #find all accounts
account.notifyOwner() 
account.save()
All three methods are asynchronous and return a jQuery promise which resolve with either the results or itself for chaining. Because there are multiple accounts involved, we will also need to take advantage of jQuery.when.

Here is how we implement the business logic in Coffeescript

Account.find()
  .then (accounts)->
     $.when (account.notifyOwner() for account in accounts)...
  .then (accounts...)->
     $.when (account.save() for account in accounts)...
  .done ->
     console.log 'successfully notified all owners'
It takes advantage of coffeescript's splats feature to overcome jQuery.when's inability to take in an array of deferred.
  #resolves only when all three deferred resolve) 
  jQuery.when(deferred1, deferred2. deferred3) 
  
  #immediately resolves with the array passed in) 
  jQuery.when([deferred1, deferred2. deferred3]) 
What Javascript does that Coffeescript translate to?
Account.find().then(function(accounts) {
  var account;
  return $.when.apply($, (function() {
    var _i, _len, _results;
    _results = [];
    for (_i = 0, _len = accounts.length; _i < _len; _i++) {
      account = accounts[_i];
      _results.push(account.notifyOwner());
    }
    return _results;
  })());
}).then(function() {
  var account, accounts;
  accounts = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
  return $.when.apply($, (function() {
    var _i, _len, _results;
    _results = [];
    for (_i = 0, _len = accounts.length; _i < _len; _i++) {
      account = accounts[_i];
      _results.push(account.save());
    }
    return _results;
  })());
}).done(function() {
  return console.log('successfully notified all owners');
});
If you don't mind a bit help from underscore, We probably can simplify the iteration logic:
Account.find().then(function(accounts) {
  var account;
  return $.when.apply($, (function() {
    return _(accounts).map(function(account){
      return account.notifyOwner();
    });
  })());
}).then(function() {
  var accounts = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
  return $.when.apply($, (function() {
    return _(accounts).map(function(account){
      return account.save();
    });
  })());
}).done(function() {
  return console.log('successfully notified all owners');
});
So, that's clearly a lot more key strokes than Coffeescript but that's not the point. The point is how easy to read this Javascript version vs the Coffeescript version. Code could be written only once but it often times has to be read multiple times afterwards. Which version will you rather read?

Now, are you convinced?