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 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.
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.)
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
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"
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
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:
- fetch the users from the server using our promise (line 2)
- show the layout (lines 4-6) so we can show other views within it
- 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.