2

I have a backbone app that loads some data from an API into a chart. This app has several tabs that can be navigated through to access these different charts, and every tab executes a route. Every chart is an instance of ChartView with the appropriate data put in.

I have a problem that is caused by some API calls that may take a while. When the requests takes too long, some users start to cycle quickly through the tabs, executing each route quickly after each other. This fires up all the collection fetches which eventually messes up the interface because some callbacks will do a bit of rendering.

So my question is, how can i make sure that every time a new route is loaded (even if it is done in quick succession) that all pending or started requests are stopped so no "request-success" callbacks are fired?

jaapz
  • 989
  • 8
  • 26

2 Answers2

1

I would suggest, override Backbone.Views remove method. With regular stopListening, abort ajax calls, also set a flag like this.removed=true. In your render function check for removed flag, if present don't render. If click has been done very quickly, you may need to check it before making any calls.

Ravi Hamsa
  • 4,721
  • 3
  • 14
  • 14
  • I have already overriden the remove method, but afaik the base remove method of views should already automatically call stopListening. Adding it did not change anything for me. I will try the flag idea, thanks! – jaapz Jul 22 '14 at 07:13
1

Based on Ravi Hamsa's reply I implemented an object that is injected into each route that keeps the requests and whether or not the route is still relevant.

It looks like this:

var RouteContext = function RouteContext() {
    this._xhrs = {};
    this.stopped = false;

    this.manageRequest = function(xhr) {
        this.xhrs.push(xhr);
    }

    this.stop = function() {
        this.stopped = true;
        _.invoke(this.xhrs, 'abort');
    }
}

I override the Backbone.Router route method like this:

route: function(route, name, callbackFactory) {
    var callback;

    if (_.isFunction(callbackFactory)) {
        var context = new RouteContext();
        callback = callbackFactory(context);

        // When a new route is opened, this route should be stopped and all
        // corresponding jqXHR's should be aborted.
        App.mediator.on('tabClicked', function() {
            context.stop();
        });
    } else {
        callback = callbackFactory;
    }

    return Backbone.Router.prototype.route.call(this, route, name, callback);
}

I can now create a new route method with this context like this:

var routeFactory = function(routeContext) {
    // Might do some route initialisation here.

    return function() {
        this.reset(routeContext);
        // This function is the actual function that will be called when a route is triggered.

        if (routeContext.stopped === false) {
            myView.renderChart();
        }
    }
};

// Register the route on the router.
myRouter.route('route', 'name', routeFactory); 

Because a route can be called multiple times I reset the RouteContext back to it's original state when the route is called again.

And in my route I keep checking everywhere I need to do rendering whether the routeContext.stopped is still false. If it is true I don't do the rendering.

jaapz
  • 989
  • 8
  • 26