javascriptjqueryjquery-deferred

Chaining jQuery.Deferred


I have got two JS functions, both return jQuery's promise. Let's say they are: getServerTimestamp() and, say, doSomething().

getServerTimestamp(), on its first call, basically

  1. gets the current time on server via ajax
  2. works out difference between local and server time
  3. stores the time difference locally
  4. return the server time using deferred.resolve()

If getServerTimestamp() gets called again later, it will check if there is a time difference stored.


So I have to call doSomething() in a function which depends on getServerTimestamp(), and because getServerTimestamp() is returning promise, I have to also return a promise for the function. In short, my function looks a bit like this: (I admit I am not great working with multiple deferreds and promises.)

function myFunction() {
  var dfd = jQuery.Deferred();
  getServerTimestamp().then(
    function (serverTime) {
      doSomething().then(
        function (...) {
          ...
          dfd.resolve(...);
        },
        function (errorMessage) {
          ...
          console.log(errorMessage);
          dfd.reject(errorMessage);
        }
      );
    },
    function (errorMessage) {
      ...
      console.log(errorMessage);
      dfd.reject(errorMessage);
    }
  );
  return dfd.promise();
}

I am thinking chaining all the promises this way looks a bit clunky, especially the error handling parts. I am wondering if there is an elegant way of doing this.

Another thing I have in mind is that - because getServerTimestamp() practically only makes ajax call once, it would just work everything out locally without further ajax call after its first successful return. In terms of having good code, would it be a better practice if I break down and factor out getServerTimestamp() and do it like the following?

function myFunction() {
  var dfd = jQuery.Deferred();
  if (isTimeDifferenceReady()) {
    doSomething(getServerTimestampFromLocal());
    dfd.resolve(...);
  } else {
    getServerTimestamp().then(function() {
      doSomething(serverTime);
      dfd.resolve(...);
    }, ...);
  }
  return dfd.promise();
}

This way, I don't have to worry about handling errors from getServerTimestamp() when ajax call is not needed. But I feel like writing the code twice this way. Or should I just forget about making a mess this way? Would there be a more elegant alternative?


Solution

  • I would strongly recommend against tracking the state of the promise with the isTimeDifferenceReady() function. This would add a lot of unnecessary complexity to your code. One of the beautiful things about Promises is that they allow the caller not to worry about state concerns like whether or not some data needs to be fetched. The caller can rely on the fact that the Promise will resolve when the data is available; and that may be almost immediately or after some longer time.

    However, we can simplify your myFunction.

    Since getTimestamp and doSomething already return a deferred promises, we do not need to create a new one within myFunction. Furthermore, I do not think there is any value to the error handlers within myFunction because they are just logging the error and then re-rejecting it. If we removed these handlers, the caller would still need to handle these errors.

    Therefore, myFunction would have the same contract with its callers if it were simplified to:

    function myFunction() {
      return getServerTimestamp().then(doSomething);
    }
    

    The only behavioral change is that it does not log the error if either getServerTimestamp or doSomething reject with one. If you really want myFunction to log errors, you could do that with:

    function myFunction() {
      return getServerTimestamp()
        .then(doSomething)
        .catch(err => {
          console.log(err);
          throw err;
        });
    }
    

    Here is an example fiddle.