IMA-WorldHealth / bhima

A hospital information management application for rural Congolese hospitals
GNU General Public License v2.0
215 stars 103 forks source link

Use timeout to cancel heavy $http requests if the user changes pages #2793

Open jniles opened 6 years ago

jniles commented 6 years ago

Looking through ui-grid's large dataset documentation, I noticed this interesting pattern:

  var canceler = $q.defer();
  $http.get('/data/10000_complex.json', {timeout: canceler.promise})
    .then(function(response) {
      $scope.gridOptions.data = response.data;
    });

  $scope.$on('$destroy', function(){
    canceler.resolve();  // Aborts the $http request if it isn't finished.
  });

According to the AngularJS documentation for $http the timeout can be either a promise or number and will abort the XHR request when fulfilled. This means that modules that make large requests (read: Journal, General Ledger, etc) could benefit from canceling their requests if the user navigates away.

I'm not sure what affect this will have on NodeJS. It would be worth trying it out to see if we get a performance benefit.

jeremielodi commented 5 years ago

I found this possibility to stop a request from the server side to

var clientCancelledRequest = 'clientCancelledRequest';

function cancellableAPIMethodA(req, res, next) {
    var cancelRequest = false;

    req.on('close', function (err){
       cancelRequest = true;
    });

    var superLargeArray = [/* ... */];

    try {
        // Long processing loop
        superLargeArray.forEach(function (item) {
                if (cancelRequest) {
                    throw {type: clientCancelledRequest};
                }
                /* Work on item */
        });

        // Job done before client cancelled the request, send result to client
        res.send(/* results */);
    } catch (e) {
        // Re-throw (or call next(e)) on non-cancellation exception
        if (e.type !== clientCancelledRequest) {
            throw e;
        }
    }

    // Job done before client cancelled the request, send result to client
    res.send(/* results */);
} 
jeremielodi commented 5 years ago

function cancellableAPIMethodA(req, res, next) {
    var cancelRequest = false;

    req.on('close', function (err){
       cancelRequest = true;
    });

    var superLargeArray = [/* ... */];

    var promise = Q.when();
    superLargeArray.forEach(function (item) {
            promise = promise.then(function() {
                if (cancelRequest) {
                    throw {type: clientCancelledRequest};
                } 
                /* Work on item */ 
            });
    });

    promise.then(function() {
        // Job done before client cancelled the request, send result to client
        res.send(/* results */);
    })
    .catch(function(err) {
        // Re-throw (or call next(err)) on non-cancellation exception
        if (err.type !== clientCancelledRequest) {
            throw err;
        }
    })
    .done();
}
jniles commented 5 years ago

@jeremielodi, that is interesting! It also looks pretty complex to implement and probably wouldn't work with async/await... :/

For what it is worth, this is the proposal implemented in the complex vouchers case. I still haven't decided if it actually increases performance or not, but when you cancel on the client side, it seems to cancel on the server side as well.