Generic XHR caching function
jQuery already has a caching mechanism - and I'm not just talking cache: true (which as you say, only works for HEAD/GET, and relies on the browser's built-in caching).

What I'm talking about is promises. Promises are caches themselves, and $.ajax returns a promise. Once resolved or rejected, it'll stay that way, and you can keep calling done/fail/then/always on it, being passed the same arguments every time. The request isn't repeated.

So to start, your example code can be written as just:

var xhr = $.ajax.bind(this, {
    url:'/echo/json/',
    data: data,
    type: 'POST'
})();

xhr.done(function (response) { ... });
xhr.done(function (response) { ... });
xhr.done(function (response) { ... });
// etc...


As long as you have a reference to xhr, the request and response is cached.

The only difference from yours is that the response is passed as-is without the deep copy. But if your requests are JSON, you can always get a pristine response by re-parsing the response yourself:

xhr.done(function (response, status, xhr) {
  var pristineResponse = JSON.parse(xhr.responseText);
});


Incidentally, your example didn't make much sense. You called getData a couple of times but didn't actually do anything with those calls. Besides, calling it from inside the done callback, seems redundant to me:

getData('foo', getter).done(function (response) { // &lt;- the response is right there, no caching necessary
    // do stuff with response. For instance, a deep copy.
});


Not to be harsh, but your current implementation is just kinda pointless, it seems to me. You have to create the getter function, and then manually pass it to getData. Then, if you want to use the cache... you have to pass the getter function again? Shouldn't that just be getData('foo') without the getter if you want the cache? As mentioned, if you have the request's promise, you have a cache already.

However, even if you just call getData('foo'), you have to be prepared to handle a failure, if nothing's been cached. Which, in turn, means that you have to always be ready to perform the request, so you code will be littered with something like this:

getData('foo')
  .done(function (response) {
    // with no existing cache, this won't be called
  })
  .fail(function () {
    // instead we'll have to create the cache
    var getter = $.ajax.bind(something, { // oops, what "this" should we bind to?
      url:'/echo/json/',
      data: data,
      type: 'POST'
    });
    getData('foo', getter).done(function (response) {
      // finally! We can get our response
    });
  });


And you'd have to do this every time, because you don't know if you have a cache or not.

Finally, there's a risk of a race condition. You only set the cache when the request is completed successfully, so if you were to do this:

var getter = $.ajax.bind(this, {
    url:'/echo/json/',
    data: data,
    type: 'POST'
});

// This performs a request. It's asynchronous, so the code here just continues on immediately...
getData('foo', getter);

// ... so this performs *another* request, because nothing's been cached yet
getData('foo', getter);

// Time passes, and the first request completes. A cache is finally created.
// Then the second request completes, and overwrites the just-created cache.


I imagine that's why your example only tried using the cache inside the first done handler. But that's the one place where you don't need the cache, since you have the response.

So, really, I see little benefit in using your code. The only thing it seems to add is the deep copy.

If you want a simpler caching mechanism, I'd suggest something like this:

  var cache = {};
  function cachedAJAX(options) {
    if(!options.cacheKey) {
      return $.ajax(options); // no caching requested; just pass through
    }

    if(!cache[options.cacheKey]) {
      cache[options.cacheKey] = $.ajax(options); // create and cache a new request
    }

    // return the request
    return cache[options.cacheKey];
  };


Now, instead of calling $.ajax, you'd call cachedAJAX, but you'd use all the same options (url, type, data, etc.). There's just one extra option: cacheKey. If that's set, cachedAJAX won't perform a new request if it has a cached one. And it caches immediately, meaning you might cache a soon-to-be-failed request. But there's no race condition.