In the last post, we saw how to use jQuery’s promises to render a view once data had been returned from an asynchronous function. Let’s take the concept a little further and see how we could render the view only once we have data from multiple async sources.
(The code structure Brian uses is much better, the code below has been simplified in an attempt to make the concepts easier to understand.)
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.
App.reqres.setHandler "user:entities", -> defer = $.Deferred() users = new Entities.UsersCollection users.fetch success: -> defer.resolve(users) defer.promise()
And now, within the entities folder in a “statuses.js.coffee” file, let’s add another entity that will return information we’ll need in our view:
App.reqres.setHandler "daily:status", -> defer = $.Deferred() determineStatus = -> defer.resolve("today's status") setTimeout determineStatus, 3000 defer.promise()
This vital bit of code waits 3 seconds, then returns a message, thereby simulating a function requiring a long time to return (slow server, long computation, etc.). Naturally, this function could return any sort of data (e.g. Backbone model/collection) and could obtain it from the server as above.
Note: both of our functions return promises.
Synchronizing with promises
In the previous post, we saw how we could display a loading view, and replace it once the data had been fetched from the server. What if we needed to wait for multiple data sources to be available before showing our views? Well, it might involve promises…
Before we get into the thick of things, there’s one thing you must keep in mind (from the jQuery API): once the object has entered the resolved or rejected state, it stays in that state. Callbacks can still be added to the resolved or rejected Deferred — they will execute immediately.
listUsers: -> users_fetched = App.request "user:entities" status_computed = App.request "daily:status" status_computed.done (status) -> console.log "first promise resolved with: ", status users_fetched.done (users) => console.log "users fetched: ", users $.when(users_fetched, status_computed).done (my_users, my_status) -> console.log "we got both !" console.log "our users: ", my_users console.log "today's status: ", my_status users_fetched.done (fetched_users) => console.log "callbacks on resolved promises fire immediately: ", fetched_users
Quite a few callbacks get registered here, to give you an idea of how things work. In addition, the variable names used are different each time only to clarify binding: normally you’d probably just use “users” and “status”.
So what’s going on?
- On lines 2-3, we call our methods and get our promises
- We then register a callback that is to fire as soon as we get the “status” back from our asynchronous call (lines 5-6)
- We write another callback to display our users as soon as we have them (lines 8-9)
- Line 11 is where is starts to get really interesting: we want this bit of code to be executed ONLY once we have our users AND the status message. It’s important to note that since our status message gets returned with a 3 second delay, the users will be available much sooner. In other words, line 11 will wait for the slower of the 2 functions to return before executing the registered callback.
- Lines 13-14: the data returned with our calls to “resolve” on the deferred objects is displayed in the console.
- Lines 15-16 demonstrate 2 things: a) callbacks registered on resolved promises fire immediately, b) the data returned by the “resolve” call is still available.
So the callbacks would fire in this order (remember the status message returns after our users):
- Line 8
- Line 5
- Lines 12, 13, 14
- Line 15 (because users have been available since step 1.)
Using promises in this way is particularly handy when dealing with multiple data sources that must all be available before a view is rendered. Doing this with callbacks gets difficult very quickly. So if (e.g.) we had a dashboard view that had to be displayed after multiple data source were available, we’d simply put the code within the “done” callback on line 11.