Writing clean code with nested promises

I am writing an application that speaks with Apple to check out Recipipts. They have both a sandbox and a production address to which you can send a message.

When communicating with Apple, if you get the status of 21007, it means that you send to the production URL when you need to send it to an isolated sandbox.

So, I wrote code to facilitate the retry logic. Here is 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 repeat part in the validation function is pretty ugly and hard to read with nested promises. Is there a better way to do this?

+5
javascript promise
Apr 09 '13 at
source share
4 answers

You can resubmit the error in the rejection handler to continue rejecting the promise, or you can return a new promise to replace the rejection.

 exports.verify = function(data) { return postToService(data, "https://production-url.com") .fail(function(err) { if (err.code === 21007) { return postToService(data, "https://sandbox-url.com") } else { throw err } }); }; 
+5
Apr 10 '13 at 0:05
source share

Here are a couple of possibilities. Since this question has an element of personal taste for it, you may or may not like what you see!

(Login - I have not tested this code)

Option 1 - Use a wrapper for resolve and reject . This adds โ€œnoiseโ€ in the form of auxiliary functions, but improves the rest.

 var resolve = function (deferred, ob) { return function () { deferred.resolve(ob); }; }; var reject = function (deferred, ob) { return function () { deferred.reject(ob); }; }; exports.verify = function(data) { var deferred = Q.defer(); postToService(data, "https://production-url.com") .then(resolve(deferred, body)) .fail(function(err) { if (err.code === 21007) { postToService(data, "https://sandbox-url.com") .then(resolve(deferred, body)) .fail(reject(deferred, err)); } else { deferred.reject(err); } }); return deferred.promise; }; 

Option 2 - Use binding. This has the advantage of using existing JS functions, but when creating callbacks, you have duplicate references to deferred .

 exports.verify = function(data) { var deferred = Q.defer(); postToService(data, "https://production-url.com") .then(deferred.resolve.bind(deferred, body)) .fail(function(err) { if (err.code === 21007) { postToService(data, "https://sandbox-url.com") .then(deferred.resolve.bind(deferred, body)) .fail(deferred.reject.bind(deferred, err)); } else { deferred.reject(err); } }); return deferred.promise; }; 

Option 3 - Use the bind and 'handle methods' (minor change to # 2).

 exports.verify = function(data) { var deferred = Q.defer(); var resolve = deferred.resolve; var reject = deferred.reject; postToService(data, "https://production-url.com") .then(resolve.bind(deferred, body)) .fail(function(err) { if (err.code === 21007) { postToService(data, "https://sandbox-url.com") .then(resolve.bind(deferred, body)) .fail(reject.bind(deferred, err)); } else { deferred.reject(err); } }); return deferred.promise; }; 

Option 4 - The monkey patch is delayed.

 function patch(deferred) { deferred.resolveFn = function (ob) { return function () { deferred.resolve(ob); }; }; deferred.rejectFn = function (ob) { return function () { deferred.reject(ob); }; }; return deferred; } exports.verify = function(data) { var deferred = patch(Q.defer()); postToService(data, "https://production-url.com") .then(deferred.resolveFn(body)) .fail(function(err) { if (err.code === 21007) { postToService(data, "https://sandbox-url.com") .then(deferred.resolveFn(body)) .fail(deferred.rejectFn(err)); } else { deferred.reject(err); } }); return deferred.promise; }; 
+1
Apr 09 '13 at 22:33
source share

You might think of the following. I think that wise use of spaces can help in readability. You will probably want to find a reasonable standard in style that your team feels good and stick to!

 exports.verify = function(data) { var deferred = Q.defer(); postToService(data, "https://production-url.com") .then(deferred.resolve, function(err) { if (err.code === 21007) { postToService(data, "https://sandbox-url.com") .then(deferred.resolve, deferred.reject); } else { deferred.reject(err); } }); return deferred.promise; }; 
0
Apr 09 '13 at 22:33
source share

Stuart's answer is right, the point is in the chain of promises. and I would like to clarify that you do not need to use Q.defer just for packaging. he was even considered an anti-pattern. See Reasons here. Delayed anti-pattern.

 var request = require('request') , Q = require('q'); var PRODUCTION_URL = "https://production-url.com", var SANDBOX_URL = "https://sandbox-url.com", export.verify = function() { return postToProduction(data) .fail( function(error) { if (error.code === 21007 ) return postToSanbox(data); throw error; }); } function postToProduction(data) { return postToService(data, PRODUCTION_URL); } function postToSandbox(data) { return postToService(data, SANDBOX_URL); } function postToService(data, url) { var deferred = Q.defer(); var options = { data: data, url: url }; request.post(options, function(err, response, body) { if (err) return deferred.reject(err); if (hasErrors(response)) return deferred.reject(response); deferred.resolve(body); }); return deferred.promise; } 
0
Apr 07 '15 at 17:12
source share



All Articles