Backbone with jQuery promises

24 Apr 2014

Handling data is one of the main hogs in web developer's productivity. Creating a wonderful UI is hard enough, now add on top of it the fact that you sometimes you have to deal with unconventional servers. Unfortunately, Backbone (and to some extent, JavaScript as a whole) is really bad at telling you the state of when you have your data. This usually leads people to create their own "API"s for handling data. Here's one simple way I've used in my applications to abstract that part of development.

To begin without the use of promises, beginners to Backbone end up writing smelly code like this:

showAnimals: function() {
  if (collection.length){ // assume collection has loaded
    // do something crazy
  } else {
    this.listenToOnce(collection, "sync", this.showAnimals);
    collection.fetch();
  }
}

First of all, I'm not a fan of articles that tell you a piece of code is bad without explaining why. The code above is particularly frustrating because it doesn't handle the use case where you fetch a collection off the server, but only to see that the collection is still empty. What does this lead to? An (albeit, relatively slow) infinite loop.

Which leads to the following kludge:

showAnimals: function() {
  if (collection.length && !this.alreadyBeenHere){
    // do something crazy
  } else {
    this.listenToOnce(this.collection, "sync", this.showAnimals);
    collection.fetch();
    this.alreadyBeenHere = true; // gross
  }
}

Calling that method adds an unrelated property as a side effect, which might lead to future bugs, especially if you have other methods that need to follow the same logic as showAnimals.

A better way is to create an abstraction for your app to work with getting data from the server is to 1) leave the data management to another object and 2) use promises:

App.sampleDataHandler = {
  store: {}, // will hold all of the collections/models
  getAnimals: function(){
    if (this.store["animals"] == null){
      var animals = new App.Collections.Animals(); // create collection of animals
      this.store["animals"] = animals;
      return animals.fetch(); // returns a promise as well
    } else {
      return (new $.Deferred()).resolve(this.store["animals"]);
    }
  }
}

This lets you write refactor your showAnimals code above:

showAnimals: function() {
  App.sampleDataHandler.getAnimals().then(function(animalsCollection){
    // do something crazy
  });
}

While this is a great start to abstracting data management, one particular problem is that the signature (specifically, the return values) of the getAnimals method is inconsistent. When the fetch is returned, the object that is passed into the queued thens or dones is different (it resolves with the ajax response) then when the created Deferred is returned, therefore:

App.sampleDataHandler = {
  store: {}, // will hold all of the collections/models
  getAnimals: function(){
    var self = this,
        d = new $.Deferred();
    if (this.store["animals"] == null){
      var animals = new App.Collections.Animals(); // create collection of animals
      this.store["animals"] = animals;
      animals.fetch().then(function(){
        d.resolve(self.store["animals"]);
      });
    } else {
      d.resolve(this.store["animals"]); // resolve now with already stored collection
    }
    return d;
  }
}

It would be interesting to create a library that abstracts this elegantly for you using namespacing, but I can see issues come up in handling object permissions of data management without touching the Backbone.View class. In the Ember.js framework, all of the ORM-like abilities is abstracted in Ember-Data. and they do a pretty solid job of securing permissions.