Skip to content

Instantly share code, notes, and snippets.

@mikeobrien
Created February 22, 2012 07:04
Show Gist options
  • Save mikeobrien/1882828 to your computer and use it in GitHub Desktop.
Save mikeobrien/1882828 to your computer and use it in GitHub Desktop.
Error handling with backbone.js + fubumvc
# 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
// 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)));
}
}
@mikeobrien
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment