Using jQuery promises to render Backbone views after fetching data

So I’ve been working through Brian Mann’s Marionette screencasts at Backbonerails.com. They’re absolutely top notch material, and I can’t recommend them enough. So if you want to learn how to go about building large Backbone applications (even if the back end won’t be rails), check them out!

In episode 6, Brian goes over several strategies for fetching remote data and updating the views in consequence. He also mentions using jQuery promises,  which isn’t shown in the screencast. So I thought I’d give it a go and attempt implementing it. Unfortunately, since this blog post depends directly on Brian’s screen cast and code, it might be harder to follow along if you don’t have them handy.

Update (June, 2013): I’ve written a book on Marionette. It guides you through developing a complete Marionette application, and will teach you how to structure your code so large apps remain manageable.

The goal

The functionality we’ll implement using promises will be a “loading” view indicating the user data is being loaded. Once the app has fetched the remote data, the loading view will be replaced by the appropriate views.

(Note we have a “sleep 3” command in the “index” action in “app/controllers/users_controller.rb” to simulate a slow server response and to enable us to see how the app responds.)

Deferreds and promises for the layman

When working asynchronously (usually fetching data and interacting with the DOM when said data is available), the usual technique is to provide callbacks, in essence saying “get this data, and when it’s available, do this with it”. But when multiple data sources and callbacks get involved, it becomes increasingly difficult to maintain sanity. Is there a better way to deal with these cases?

That’s where deferreds and promises come in. Simply put, promises give you a handle on the state of a function: if it’s still in progress, the callbacks will be put in a queue; if the function is already in a “resolved” state and you add a callback, it’ll be run immediately. For more details, see the documentation.

Implementation

So, let’s make our user entities handler return a deferred:


getUserEntities: ->
  defer = $.Deferred()
  users = new Entities.UsersCollection
  users.fetch
    success: ->
      defer.resolve(users)
  defer.promise()

So, what’s going on? On line 2, we declare a deferred object, which will basically be used to refer to the state of our data fetching (i.e. if it’s done or not). Then, we proceed to fetch the data, specifying on lines 5 and 6 (in the fetch method’s success callback) that our deferred object should be marked as resolved when when get the user data from the server, and that the data should be passed on. Last, but not least, we return a promise on line 7. (A promise is  basically a “read only” copy of our deferred object: it’s used to monitor the state of the function, but can’t be updated.)

Note that we are no longer returning the “users” collection, but a jQuery promise. Let’s get down to using this promise in our user’s “list” controller (in app/assets/javascripts/backbone/apps/users/list/list_controller.js.coffee) :

listUsers: ->
  fetching_users = App.request "user:entities"

  console.log "fetching users from server"

  fetching_users.done (users) ->
    console.log users

Here, we get the promise and store it as “fetching_users”. We then log out to the console to demonstrate that the promise is fully asynchronous and doesn’t block code execution. We then indicate (on lines 6-7) that when our promise returns the user data, it should be logged out to the console. The “returns the data” magic is due to the “resolve” call we have on our deferred object (see above): it’s called when the fetch method returns the data, and it passes the result as the argument to “resolve”, making it available to the “done” method we’ve called on line 6.

If you refresh the app, you’ll see the loading message in the console, followed by a few seconds of waiting (due to the server side “sleep” call simulating a slow response), and then you’ll see the user data dumped out to the console. Now that we know we have our promise working, let’s get the views working.

Adding the views

Since we’re now using a promise to prevent our user view from being displayed before the data has been fetched from the server, we no longer need to rerender the user panel when the collection is reset. So we can remove the collectionEvents object from our view definition (in “app/assets/javascripts/backbone/apps/users/list/list_view.js.coffee”):

class List.Panel extends App.Views.ItemView
  template: "users/list/templates/_panel"

While we’re in that file, let’s go ahead and add a new ItemView for our loading view in that same file:

class List.Loading extends App.Views.ItemView
  template: "users/list/templates/_loading"

Which means we need to add the loading template at “app/assets/javascripts/backbone/apps/users/list/templates/_loading.jst.eco” :

LOADING USER DATA...

Now that the views have been added, we still need to wire them up to make them functional…

Wiring up the views in the controller

To get the loading view to behave as we want it to, we need the following code in our controller (at “app/assets/javascripts/backbone/apps/users/list/list_controller.js.coffee”):

    listUsers: ->
      fetching_users = App.request "user:entities"

      @layout = @getLayoutView()

      App.mainRegion.show @layout
      @showLoading()

      fetching_users.done (users) =>
        @showPanel users
        @showUsers users

    showLoading: ->
      loadingView = @getLoadingView()
      @layout.usersRegion.show loadingView

    getLoadingView: ->
      new List.Loading

Lines 13-18 are pretty straightforward functions to get the loading view and show it. Within the “listUsers” function, we do the following:

  1. fetch the users from the server using our promise (line 2)
  2. show the layout (lines 4-6) so we can show other views within it
  3. show our loading view within the layout (line 7)

The above actions will happen uncondtionally: the loading view will be displayed, however briefly. If we stopped here, it would be displayed forever. So how do we show our “users” and “panel” views when the data is returned? By using the promise, of course!

As you can see on lines 9-11, once the user data has been fetched we show the views requiring the user data. And as you can see after refreshing, at no point does our panel show there are 0 users, even though we no longer bind to the “users” collection’s events: the views are rendered only once we have all the data on hand.

(As a side note, pay attention to line 9: we’re using Coffeescript’s fat arrow to preserve context and be able to call the “@show…” functions.)

Want to use promises a little better? I’ll show you how they can be used to wait for multiple async functions to return before executing a callback in the next post.

This entry was posted in AJAX, Backbone.js, Backbone.Marionette, jQuery. Bookmark the permalink.

2 Responses to Using jQuery promises to render Backbone views after fetching data

  1. Arturo says:

    Actually, the fetch method of a Backbone collection returns a jqXHR object, which is already a promise (since jQuery 1.5). So you can achieve similar results with only:


    var promise = collection.fetch();

    promise.done(function () {
    // render view here
    });

  2. david says:

    Hi Arturo, you’re absolutely right: Backbone.sync returns the result of jQuery’s $.ajax call, which is a promise. However, in the above app’s case, the users collection is defined elsewhere and returned using Marionette.RequestResponse (https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.requestresponse.md). Since I don’t have the users collection, I needed the call to resolve to return the actual collection data.

Comments are closed.