Using jQuery promises to render Backbone views after fetching data

Posted on April 1, 2013

 This article is part of a series on dealing with async data in Marionette views.

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 there, 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 article is part of a series on dealing with async data in Marionette views.


Would you like to see more Elixir content like this? Sign up to my mailing list so I can gauge how much interest there is in this type of content.