Created
February 22, 2012 07:04
-
-
Save mikeobrien/1882828 to your computer and use it in GitHub Desktop.
Error handling with backbone.js + fubumvc
This file contains hidden or 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
# Proxies ajax errors to the event aggregator | |
define 'ajaxerrorproxy', ['jquery', 'postal'], ($, postal) -> | |
options = {} | |
$(document).ajaxError (error, xhr, settings, thrownError) -> | |
json = !xhr.getResponseHeader('content-type').indexOf('application/json') | |
status = options[xhr.status] ? {} | |
channel = postal.channel "error.ajax.#{status.alias ? xhr.status}" | |
channel.publish | |
status: status.alias ? xhr.status | |
message: status.message ? thrownError | |
data: if json then $.parseJSON(xhr.responseText) else xhr.responseText | |
options | |
# App main | |
require ['app', 'ajaxerrorproxy'], (app, ajaxErrorProxy) -> | |
# Optional conventions for statuses and messages | |
ajaxErrorProxy[0] = message: 'A network error has occurred.' | |
app.start() | |
# Global error view | |
define 'errorview', ['jquery', 'postal'], ($, postal) -> | |
class ErrorView extends Backbone.View | |
initialize: (options) -> | |
... | |
# Subscribe to all ajax errors | |
postal('error.ajax.*').subscribe @render | |
render: (error) -> | |
error = $ @template { error: error } | |
@$el.append error | |
error.fadeIn 'slow' | |
error.delay(3000).fadeOut('slow').hide | |
# Any old form view | |
class AddFormView extends Backbone.View | |
events: | |
'click .save': 'save' | |
... | |
save: -> | |
# Handle success logic here | |
@collection.add ..., success: -> @router.navigate '/', trigger: true |
This file contains hidden or 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
// Handle exceptions in a behavior(s) instead of returning them from an action | |
public class AjaxExceptionHandlerBehavior : IActionBehavior | |
{ | |
... | |
public void Invoke() | |
{ | |
try | |
{ | |
_innerBehavior.Invoke(); | |
} | |
catch (Exception e) | |
{ | |
if (e is ValidationException) | |
WriteResponse(HttpStatusCode.BadRequest, "A validation error has occurred", | |
((ValidationException)e).Errors); | |
else if (e is AuthorizationException) | |
WriteResponse(HttpStatusCode.Unauthorized, "You are not authorized to perform this action."); | |
else | |
{ | |
# We only want to log exceptions that we consider unhandled | |
_logger.LogException(e); | |
WriteResponse(HttpStatusCode.InternalServerError, "A system error has occurred."); | |
} | |
} | |
} | |
public void WriteResponse(HttpStatusCode status, string description, IList<string> errors = null) | |
{ | |
_outputWriter.WriteResponseCode(status); | |
_outputWriter.WriteResponseDescription(description); | |
if (errors != null) _outputWriter.Write(MimeType.Json, JsonUtil.ToJson(errors)); | |
} | |
... | |
} | |
public class PostHandler | |
{ | |
... | |
public Model Execute(Model request) | |
{ | |
// Adds the item and returns it with the id populated | |
// so backbone can update its model. If there is an | |
// exception thrown in the application layer | |
// (e.g. validation, auth, etc) it will get bubbled | |
// up to the AjaxExceptionHandlerBehavior. | |
return Map(_addWidgetService.Add(Map(request))); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
So here is something along the lines of what I would do in a Fubu+Backbone app.
On the client side there is an AMD module that proxies ajax errors to the event aggregator (Which is similar to what you're doing I think). Allows you to have some conventions for overriding the status and description (Which can be set in your app main). The error view is also an AMD module, so DRY. It subscribes to all ajax errors and renders them (Or you could have it only subscribe to certian ones. For example if you wanted to throw up the login page on a 401 so the user could log back in w/o losing her work). Views specify what happens on success, thats all they care about. Also they are not directed by the server, they know what to do next. A coworker is playing with the jquery deffered stuff right now for more complicated success scenarios, looks pretty sweet. If for some reason you do want to handle an error locally, you just pass in an error handler into the bacbone add/fetch/yada method, otherwise it's just handled globally.
On the server side you have an ajax exception handler behavior that knows about errors it can handle by setting the status, description and optional response (Which is similar to the errors array in the ajax continuation). Exceptions that it doesent know about default to 500 and are logged (the others are obviously not logged). I like how the behavior DRY's up the error handling so I don't have to do it in the action. I'm also assuming that there will only be a handful of known exceptions (At least in our app anyways) so that behavior should be pretty slim and not violate the SRP too much. The endpoint doesent care about errors, it assumes success. One of issues I have with the ajax continuation is that I will often need to return data on success. For example, adding an model to a backbone collection POST's it to the server and then expects back that model with any properties populated by the server on save (Like the id or timestamps or whatever). Same is true of other non CRUDy actions that need to return some data.
Anyways, I hope you don't get the impression that I'm just trying to debate this. That's not my intention. I'm still at a stage where I'm trying to figure out the best way to use fubu with SPA's so I'm just thinking out loud, trying to work it out. I do see how the ajax continuation can be powerful in a scenario where it's not an SPA though. That makes a lot sense.