Skip to content

Instantly share code, notes, and snippets.

@AntonNguyen
Created April 9, 2013 21:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save AntonNguyen/5349783 to your computer and use it in GitHub Desktop.
Save AntonNguyen/5349783 to your computer and use it in GitHub Desktop.
Writing Clean Code With Nested Promises

I'm writing an app that talks to Apple to verifyReceipts. They have both a sandbox and production url that you can post to.

When communicating with Apple, if you receive a 21007 status, it means you were posting to the production url, when you should be posting to the sandbox one.

So I wrote some code to facilitate the retry logic. Here's a simplified version of my code:

var request = require('request')
  , Q = require('q')
  ;

var postToService = function(data, url) {
  var deferred = Q.defer();
  var options = {
    data: data,
    url: url
  };

  request.post(options, function(err, response, body) {
    if (err) { 
      deferred.reject(err);
    } else if (hasErrors(response)) {
      deferred.reject(response);
    } else {
      deferred.resolve(body);
    }
  });

  return deferred.promise;
};

exports.verify = function(data) {
  var deferred = Q.defer();

  postToService(data, "https://production-url.com")
    .then(function(body) {
      deferred.resolve(body);
    })
    .fail(function(err) {
      if (err.code === 21007) {
        postToService(data, "https://sandbox-url.com")
          .then(function(body){
            deferred.resolve(body);
          })
          .fail(function(err) {
            deferred.reject(err);
          });
      } else {
        deferred.reject(err);
      }

    });

  return deferred.promise;
};

The retry portion in the verify function is pretty ugly and difficult to read with the nested promises. Is there a better way of doing this?

@jonathanong
Copy link

why even bother using promises for something this simple?

@RobertLowe
Copy link

Try something like this:

PRODUCTION_URL = "https://production-url.com"
SANDBOX_URL    = "https://sandbox-url.com"

verify = (data, url) ->
  deferred   = Q.defer()
  url      ||= PRODUCTION_URL

  postToService(data, url).then((body) ->
    deferred.resolve body
  ).fail (err) ->
    if err.code is 21007
      verify(data, SANDBOX_URL)
    else
      deferred.reject err

  deferred.promise

You'll have to excuse my coffeescript

Cheers

@jdiamond
Copy link

I think this behaves the same as your version:

function postToService(data, url) {
  return Q.ninvoke(request, 'post', {
    data: data,
    url: url 
  }).then(function(args) {
    var response = args[0];
    var body = args[1];
    if (hasErrors(response)) {
      throw response;
    }
    return body;
  });
}

function verify(data) {
  return postToService(data, 'https://production-url.com')
    .then(null, function(error) {
      if (error.code === 21007) {
        return postToService(data, 'https://sandbox-url.com');
      }
      throw error;
    });
}

@yocontra
Copy link

I cleaned your code a little https://gist.github.com/Contra/5359196

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment