“No! No! You can't triple stamp a double stamp! You can't triple stamp a double stamp, Lloyd!”

-Harry from Dumb and Dumber

A common problem in Backbone.js apps comes from users furiously double clicking submit buttons, resulting in multiple Ajax requests being submitted to the server.

Underscore.js has a debounce function that can be used to rate-limit multiple Ajax requests. A common approach is to wrap every click event that results in an Ajax request with the debounce function. Unfortunately, this was not an ideal solution for us because of the following:

  1. We have a very large Backbone.js app (perhaps one of the largest out there!), and combing through every single Ajax event would be tedious and not DRY. Plus, developers would have to remember to properly debounce any new submit buttons they add to the app.

  2. We could solve (1) by extending the behavior of Backbone.Model and wrap the save function with Underscore's debounce. However, because of the way debounce was implemented, the save function would be rate-limited in the global score, not the instance scope. That is, if 2 different models called save at nearly the same time, the second model's save might be ignored, even though it's a different model.

We ended up solving this problem with a third approach: extending the behavior of Backbone.Model and wrap the save function with our own version of debounce that properly scopes to each instance. Here's the code in CoffeeScript:

class DebounceModel extends Backbone.Model

  _debounceTime: 100
  _canSave: true

  save: ->
    if @_canSave
      @_canSave = false
      setTimeout((=> @_canSave = true), @_debounceTime)
      super(arguments...)

All of our Backbone.js models then inherit from DebounceModel and are automatically protected from user's sending multiple Ajax requests due to double-clicks. Problem solved!