Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catching Backbone sync errors

I needed to catch a possible login page in all responses from the server, so I have overridden Backbone.sync globally so I can check all errors before passing them on.

Backbone.originalSync = Backbone.sync;

Backbone.sync = function (method, model, options) {
    var originalSuccess, originalError;
    console.log("sync override...");
    // remember original success so we can call it if user logs in successfully
    originalSuccess = options.success;
    // proxy the error callback to first check if we get a login page back
    originalError = options.error;
    options.error = function (model, xhr, options) {
        if (xhr.status === 200 && xhr.responseText === "") {
            // parse error from empty response (jq1.9 invalid json, ok)
            originalSuccess(model, xhr, options);
        } else {
            console.log("Sync error " + statusTxt + ", " + thrown.message);
            if (xhr.status === 200 || xhr.status === 302 || xhr.status === 0) {
                // login page returned instead of json...
                // open a new window with relogon.html to trigger a new login
                window.showModalDialog("../relogon.html");
            } else {
                // normal error, pass along 
                if (originalError) {
                    originalError(model, xhr, options);
                }
            }
        }
    };

    // call the original sync
    Backbone.originalSync(method, model, options);
};

This broke miserably when going from 0.9.9 to 1.0. Looks like the original Backbone.sync wraps its error handlers differently, causing my error handler to be called first, with a jquery xhr signature. I had to change the signature of the error handler to this:

    options.error = function (xhr, statusTxt, thrown) {

Ok so now it works, but I get the feeling that I am doing something wrong.

Is there a better way to do this?

I tried with jquery promises but I need to be able to switch from error state to success (when calling originalSuccess), which did not seem to work with promises.

like image 564
d4kris Avatar asked May 10 '13 07:05

d4kris


2 Answers

All sync errors are passed to model's error event, so you may to listen to this event.

From http://backbonejs.org/#Events-catalog:

"error" (model, xhr, options) — when a model's save call fails on the server.

To capture error globally you may use http://api.jquery.com/ajaxError/

like image 75
Andrey Kuzmin Avatar answered Nov 28 '22 23:11

Andrey Kuzmin


You can build your own jQuery Deferred object to alter the default Backbone.sync behavior

Backbone.sync = function (method, model, opts) {
    var xhr, dfd;

    dfd = $.Deferred();

    // opts.success and opts.error are resolved against the deferred object
    // instead of the jqXHR object
    if (opts)
        dfd.then(opts.success, opts.error);

    xhr = Backbone.originalSync(method, model, _.omit(opts, 'success', 'error'));

    // success : forward to the deferred
    xhr.done(dfd.resolve);

    // failure : resolve or reject the deferred according to your cases
    xhr.fail(function() {
        if (xhr.status === 200 && xhr.responseText === "") {
            dfd.resolve.apply(xhr, arguments);
        } else {
            if (xhr.status === 200 || xhr.status === 302 || xhr.status === 0) {
                console.log('login');
            }
            dfd.reject.apply(xhr, arguments);
        }
    });

    // return the promise to add callbacks if necessary
    return dfd.promise();
};

The promise reflects the end state you choose.

http://jsfiddle.net/AsMYQ/4/ for a fail demo, http://jsfiddle.net/AsMYQ/5/ for a success.

And if I may

  • you probably should not tie so tightly Backbone.sync with your login handling. Use events, from Backbone or jQuery.ajaxError as @Andrey suggested
  • your server response should indicate an authorization failure, probably a 401 status
  • don't forget to return your deferred promise/jqXHR object when you override sync, that might come in handy down the line
like image 31
nikoshr Avatar answered Nov 28 '22 22:11

nikoshr