Javascript: Self-replacing Promises

My application has several resources that need to be loaded just once, and used often afterwards.

Here is a pattern that I’m finding quite useful lately:

/** 
 * Retrieve the resource once only.
 * If the resource hasn't been loaded yet, 
 *   return a deferred that resolves to the resource.
 * If the resource has been loaded, 
 *   return the resource directly.
 */ 
App.getResource = function() {
  //Return the resource or the deferred if it's set already
  if (App._resource) 
    return App._resource; 
  //Otherwise build, set and return a deferred that 
  //replaces itself when it's finished loading
  else
    return App._resource = $.getJSON("resource/url").then(
      function(data) {
        console.debug("Loaded resource");
        return App._resource = data;
      }
    );
};
  • The first time the function is called, it fetches the resource and returns a deferred object.
  • When the promise resolves, it passes the resource to handlers.
  • If called again before the promise resolves, it returns the same promise. (won’t fetch twice)
  • After the promise has resolved, the function instead returns the resource directly.
  • If the resource can’t be loaded, the promise fails instead, and is not replaced.

To use directly;

$.when(App.getResource()).done(function(resource) {
  //Use the parameter from the promise
  resource.doStuff();
});

The $.when call transparently converts a non-promise into a resolved promise with that value. The snippet behaves the same whether App.getResource() has been previously loaded or not.


So why replace the promise at all? Sometimes you need to be able to treat the resource like a synchronous value, such as within a library or a function that is called synchronously.

//Here's a class with a synchronously-called function
var Model = Backbone.Model.extend({
  ...
  validate: function(attrs) {
    if (App.getResource().exists(attrs[id])) {
      return "Model with that id already exists";
    } else {
      return null;
    }
  }
});
var model = new Model();

//Wait for everything necessary to load first.
$.when(App.getResource(), model.fetch(), ...).done(
  function() {
    if (model.isValid()) doFoo();
    else doBar();
  });

This code waits for App.getResource() to resolve, as well as for any other needed promises. Afterwards, App.getResource() can just be treated like a synchronous function.


This pattern enables flexible and transparent use of remote resources, in both synchronous and asynchronous ways.

The App.getResource() function is also quite similar to a Lazy Promise, as discussed by James Coglan.