Synchronize AJAX calls for Backbone Models and Collections
Problem: Backbone calls save, fetch and destroy concurrently on model and collection instances but we need to control the order in which they are called.
Solution: Implement a custom sync method that chains AJAX calls using jQuery Deferred.
We recently ran into a scenario where we have a Backbone model, Basket, that may be modified on the server whenever it is saved. The modifications are things like removing items that are no longer for sale or updating pricing for items currently in the basket.
We save the basket regularly but only need to show it's entire contents when the user views their basket or starts to check out. In these scenarios we re-fetch the basket to be sure that we have the most accurate and up to date information to show.
We uncovered a bug with this strategy in our integration tests. It's possible to save the basket and then immediately re-fetch it's content before the save is complete. In this case, you'll actually get the old basket state from the fetch. This is extremely rare when a user is interacting with the site because people don't usually click that fast, but our PhantomJS integration tests do - so we were seeing intermittent test failures due to unexpected basket contents coming back from the fetch.
We really want the fetch to wait until the save is complete - for AJAX calls from the basket to be synchronous, not the asynchronous default.
You can tell jQuery to make all of it's ajax calls synchronously with {async: false} but not Backbone on a per-model basis.
All Backbone ajax calls (create, save, fetch, remove) use the sync method under to hood. You can implement your own sync method to customize how those calls are made.
To accomplish to synchronize our ajax calls, we wrote a mixin (the gist above) for any Backbone model or collection that will run all ajax calls on a model instance serially instead of concurrently.
This mixin demonstrates a pattern we use commonly at Good Eggs. The mixin is a function that takes a reference to a class. It can define new prototype methods, monkey patch existing methods, define new object properties and add methods to the class.
In this case, our mixin defines a new sync method on the prototype or monkey patches it if it already exists. It uses the jQuery Deferred object returned by all ajax calls to chain calls to sync with then.
You can use this mixin to add this synchronous behavior to any Backbone model or collection.