Search code examples
jquerypromisejquery-deferred

How to handle both sync and async jQuery promises/deferreds the same way with "when" and results in different formats?


I know jQuery has a method .when() which works both asynchronously and synchronously.

If .when() is passed a jQuery promise/deffered it will be asynchronous and the .then() or .done() etc handler will get passed the promise/deferred, and that will include the asynchronous data in its format.

If .when() is passed anything else it will be asynchronous and the .then() or .done() etc handler will get passed the exact object (or anything else) you passed to when.

Now typically you get to define the synchronous object yourself but that's not always the case for the asynchronous one, which may be returned from some 3rd party web server for instance.

The point of doing this that you're going to transform some data in some way, and sometimes you can do it locally using logic, but other times you need to call out for it if your local stuff can't manage on its own.

Either way you're getting a result that you'll pass on to the rest of your code via a callback.

What's the "correct" way to handle both kinds of data in a uniform manner?

I suppose I could craft my sync object to look like the object the remote server returns, but that "smells bad".

Or the code in the callback could "sniff" the data it gets to see which kind it is, but that also "smells bad".

If this seems too simple, imagine the case where either of several 3rd party servers might be called, each of which returns a different format.

Here's the kind of thing:

$.when(getSyncOrAsync(7)).done(function(obj) {
  // check if 'obj' is in our format
  // check if it's in one.com's format
  // check if it's in two.com's format
  // code smell? how can we get here after normalizing the formats
  // ... or something? ...
});

function getSyncOrAsync(x) {
  if (x < 3) {
    // we can do it ourselves - return an object directly
    return { some: 'very', simple: 'object' };
  } else if (x < 6) {
    // we need to call site number one - return a promise/deferred
    return $.get('http://one.com#' + x);
  } else {
    // we need to call site number two - return a promise/deferred
    return $.get('http://two.com#' + x);
  }
}

Is there a third way I'm too daft to see right now? Does it involve trickery with an extra step in the promise/deferred pipeline? That seems inefficient but less "smelly". What am I missing?


Solution

  • Use .then on the $.get requests to transform the result to your expected format so that your promise callback doesn't have to have that logic.

    getAsync(7).done(function(obj) {
      console.log(obj.some); // always works
    });
    
    function getAsync(x) {
      if (x < 3) {
        // we can do it ourselves - return a new promise resolved with object
        return $.when({ some: 'very', simple: 'object' });
      }
      if (x < 6) {
        // we need to call site number one - return a promise/deferred
        return $.get('http://one.com#' + x).then(function (result) {
          return { some: result.foo, simple: result.bar };
        });
      }
      // we need to call site number two - return a promise/deferred
      return $.get('http://two.com#' + x).then(function (result) {
        return { some: result.apple, simple: result.banana };
      });
    }