Here's how I'd approach it (see ***
comments):
var makeRequest = function(method, urlToBeCalled, payload) {
var deferred = $q.defer();
var retries = 4; // *** Counter
run(); // *** Call the worker
return deferred.promise;
// *** Move the actual work to its own function
function run() {
var xhr = new XMLHttpRequest();
xhr.open(method, encodeURI(urlToBeCalled), true);
setHttpRequestHeaders(xhr);
xhr.onload = function() {
if (xhr.status === 200 && xhr.readyState === 4 && xhr.getResponseHeader('content-type') !== 'text/html') {
try {
response = JSON.parse(xhr.response);
deferred.resolve(response);
} catch (e) {
deferred.reject(e);
}
} else if (xhr.status === 0) {
// retry
if (retries--) { // *** Recurse if we still have retries
run();
} else {
// *** Out of retries
deferred.reject(e);
}
} else {
// *** See note below, probably remove this
try {
response = JSON.parse(xhr.response);
deferred.reject(response);
} catch (e) {
deferred.reject(xhr.response);
}
}
};
xhr.onerror = function() {
deferred.reject(xhr.response);
};
xhr.send(payload);
}
};
Side note: The content of your initial if
body and the final else
appear to be identical. I think I'd recast the entire onload
:
xhr.onload = function() {
if (xhr.readyState === 4) {
// It's done, what happened?
if (xhr.status === 200) {
if (xhr.getResponseHeader('content-type') !== 'text/html') {
try {
response = JSON.parse(xhr.response);
deferred.resolve(response);
} catch (e) {
deferred.reject(e);
}
} else {
// Something went wrong?
deferred.reject(e);
}
} else if (xhr.status === 0) {
// retry
if (retries--) { // *** Recurse if we still have retries
run();
} else {
// *** Out of retries
deferred.reject(e);
}
}
}
};
Re your comment:
This does resolve my current problem but is there a way to resolve all the promises which are added to call stack if any one of those is resolved?
Yes: To do that with Angular's $q
(I assume that's what you're using), you can just pass the promise you get back from the recursive call into resolve
on your deferred object: Since it's a promise, the deferred will wait for it to be settled and resolve or reject based on what that promise does. If you do this at every level in the chain, the resolutions work their way up the chain:
angular.module("mainModule", []).controller(
"mainController",
function($scope, $q, $http) {
test(true).then(function() {
test(false);
});
function test(flag) {
log(flag ? "Testing resolved" : "Testing rejected");
return recursive(3, flag)
.then(function(arg) {
log("Resolved with", arg);
})
.catch(function(arg) {
log("Rejected with", arg);
});
}
function recursive(count, flag) {
log("recursive(" + count + ", " + flag + ") called");
var d = $q.defer();
setTimeout(function() {
if (count <= 0) {
// Done, settle
if (flag) {
log("Done, resolving with " + count);
d.resolve(count);
} else {
log("Done, rejecting with " + count);
d.reject(count);
}
} else {
// Not done, resolve with promise from recursive call
log("Not done yet, recursing with " + (count - 1));
d.resolve(recursive(count - 1, flag));
}
}, 0);
return d.promise;
}
}
);
function log() {
var p = document.createElement('pre');
p.appendChild(
document.createTextNode(
Array.prototype.join.call(arguments, " ")
)
);
document.body.appendChild(p);
}
pre {
margin: 0;
padding: 0;
}
<div ng-app="mainModule">
<div ng-controller="mainController"></div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
You can do the same thing with JavaScript's own promises:
test(true).then(function() {
test(false);
});
function test(flag) {
log(flag ? "Testing resolved" : "Testing rejected");
return recursive(3, flag)
.then(function(arg) {
log("Resolved with", arg);
})
.catch(function(arg) {
log("Rejected with", arg);
});
}
function recursive(count, flag) {
log("recursive(" + count + ", " + flag + ") called");
return new Promise(function(resolve, reject) {
setTimeout(function() {
if (count <= 0) {
// Done, resolve with value
if (flag) {
log("Done, resolving with " + count);
resolve(count);
} else {
log("Done, rejecting with " + count);
reject(count);
}
} else {
// Not done, resolve with promise
// from recursive call
log("Not done yet, recursing with " + (count - 1));
resolve(recursive(count - 1, flag));
}
}, 0);
});
}
function log() {
var p = document.createElement('pre');
p.appendChild(
document.createTextNode(
Array.prototype.join.call(arguments, " ")
)
);
document.body.appendChild(p);
}
pre {
margin: 0;
padding: 0;
}