Skip to content

Instantly share code, notes, and snippets.

@DanThiffault
Last active January 25, 2017 17:34
Show Gist options
  • Save DanThiffault/55479f13196fec5abee0 to your computer and use it in GitHub Desktop.
Save DanThiffault/55479f13196fec5abee0 to your computer and use it in GitHub Desktop.
Angular $http service with automatic retries and status code based error messages
(function (app) {
// Fluent interface to generate a map of status codes to error messages
function genErrorMap() {
var statusErrorMap = { "default": { msg: "An error has occurred", retryable: false } };
var methods = {
// Add a mapping from an array of status codes to an errorMessage and whether we should retry
// the request when recieving this error status code
add: function (statusCodes, errorMessage, retryable) {
for (var i = 0; i < statusCodes.length; i++) {
statusErrorMap[statusCodes[i]] = { msg: errorMessage, retryable: retryable || statusErrorMap.default.retryable };
}
// return methods again so calls can be chained: add(...).add(...).add(...)
return methods;
},
setDefault: function(msg, retryable) {
var defaultInfo = statusErrorMap.default;
defaultInfo.msg = msg;
defaultInfo.retryable = retryable || false;
return methods;
},
value: function () { return statusErrorMap; }
}
return methods;
}
// Generate an error handler with automatic retries
function generateErrorHandler($log, errorMapping, deferred, retryFn, originalArgs, retryPosition) {
var retries = originalArgs[retryPosition] || 0;
return function (msg, code) {
$log.error(msg, code);
var thisHandler = errorMapping[code] || errorMapping.default;
if (retries < 3 && thisHandler.retryable) {
// increment retries
originalArgs[retryPosition] = retries + 1;
var chainedDefer = retryFn.apply(this, originalArgs);
chainedDefer.then(function (chainedResult) { deferred.resolve(chainedResult); },
function (chainedError) { deferred.reject(chainedError); });
} else {
deferred.reject(thisHandler.msg);
}
};
};
var todoService = function ($http, $log, $q) {
// Keep a local reference to the array so we can dynamically update it
var todos = [];
var methods = {
// Give a reference to the controller for the array we will update asyncronously
initialize: function () { return todos; },
// Refresh the list of todos and return so contollers can have a reference
index: function () {
// Create a deferred object that can be returned immediately
// We keep this separate from the $http deferred object so that we
// can do things like retry without notifying the client (in this case an angular
// controller) that we have failed until we give up completly
var deferred = $q.defer();
// Define status code to error mapping
var errorMapping = genErrorMap()
.add([401, 403], "You do not have permission to access todos")
.add([408, 503, 429], "There was a problem connecting to the server. Please refresh the page & try again after a few minutes", true)
.setDefault("There was an application error. If the problem persists please contact support")
.value();
$http.get("/api/Todos")
.success(function (data) {
todos.length = data.length;
for (var i = 0; i < data.length; i++) {
// If our data model did not match this is where we could modify it
todos[i] = data[i];
}
// Mark the async action as completed and return todos
// Likely the controller already receieved a reference to todos
// using the init method, but is here for convinience in case.
deferred.resolve(todos);
}).error(generateErrorHandler( $log, errorMapping, deferred, methods.index, arguments, 0));
return deferred.promise;
}
};
return methods;
};
// setup dependencies
// see https://github.com/johnpapa/angularjs-styleguide#style-y091 for explanation
todoService.$inject = ["$http", "$log", "$q"];
// register the factory so it can be used with dependency injection
app.factory("TodoService", todoService);
})(angular.module("todomvc",[]));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment