0

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?

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
hippietrail
  • 15,848
  • 18
  • 99
  • 158
  • 3
    `I suppose I could craft my sync object to look like the object the remote server returns, but that "smells bad".` Dude, that's the easiest solution to your problem. People have been doing this for ages. It's called interfaces. But don't just mimic the async object. Create a common interface for both of them. – freakish Apr 08 '15 at 13:16
  • That's how I'm going to hack it for now but it has a code smell. For instance say I had two different async 3rd party sources that returned different objects, there must be a less smelly way to do an interface to make those look like my sync object. But because it goes via promise/deferred I can't just `return` such an object so I'm lost ... – hippietrail Apr 08 '15 at 13:24
  • I'm not sure what kind of magic you're expecting, returning a `$.when(mySyncObject)` to simulate async where mySyncObject looks like what the remote server returns is straightforward - how else would your code know in advance what the data the remote server returns looks like? – Benjamin Gruenbaum Apr 08 '15 at 13:56
  • Returning `$.when()`? \-: I'm not expecting, I'm lost. – hippietrail Apr 08 '15 at 14:01
  • `var handleResponse = function() { $.map(arguments, function(response, index) { console.log(index, response) }) }; $.when("abc", $.get("")).then(handleResponse, handleResponse)` ? – guest271314 Apr 08 '15 at 14:16
  • Oh just calling `$.when()` then, not passing `$.when()`? – hippietrail Apr 08 '15 at 14:18
  • I added some example code if it helps ... – hippietrail Apr 08 '15 at 15:22

1 Answers1

2

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 };
  });
}
Kevin B
  • 94,570
  • 16
  • 163
  • 180
  • Yeah I was just trying to make something up that looked like something. Glad you understood anyway (-: Now since the "then"s have only a success handler that returns something, what happens at my "when" if they get a fail? I guess my "done" just doesn't get called and everything just works as I desired? Result looks too easy compared the trouble I had putting the problem in words... (-: – hippietrail Apr 08 '15 at 15:35
  • 1
    Correct, you can just use a `.fail()` after the `.done()` to catch that. Since we didn't filter or transform the error, it will pass through to the `$.when` at the top unaltered. – Kevin B Apr 08 '15 at 15:36
  • You should *always* return a promise, though. – Bergi Apr 08 '15 at 18:23
  • @Bergi for example, instead if simply returning the object in the first condition? or did you mean in the .then filters – Kevin B Apr 08 '15 at 18:25
  • @KevinB: Yes, instead of returning the plain object. Make your API consistent and always-async. See http://stackoverflow.com/q/22473728/1048572 – Bergi Apr 08 '15 at 18:27
  • Gotcha. i'll amend it. Same issue that happens when you assume something is asynchronous in node when it may not always be so. – Kevin B Apr 08 '15 at 18:28
  • Oh, I wouldn't do the `setTimeout` here but just `$.when({…})`, the promise lib should take care of the asynchrony itself. (If only jQuery was a proper one…) – Bergi Apr 08 '15 at 19:59
  • Yes, but we're using jquery here, so.... yea, :) might be overcomplicating it anyway. without .catch error handling is going to suck either way. – Kevin B Apr 08 '15 at 20:10