Last active
January 25, 2017 17:34
-
-
Save DanThiffault/55479f13196fec5abee0 to your computer and use it in GitHub Desktop.
Angular $http service with automatic retries and status code based error messages
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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