1

So, one of my $angular scopes starts a $http request upon user interaction. That request that take quite a while to run and it can happen that when its promise is resolved, the scope that was used to start the request is already gone.

Now, obviously, because js is a nice language the scope object is still there because it's been captured in the promise handler variable scope so, at worst, what is going to happen is that I am going to update the scope variables even though the scope is entirely unused. However, one of the things that can happen in this handler is this:

$state.go("^");

Now, if the ui-router state that is associated with this scope was still active, this would work fine but when the users get bored, they tend to click around and move back to another state where executing $state.go("^") is not a good idea.

I could:

  1. In the promise handler, check that the current state is still "the same" before .go("^") (I will skip on what "the same" really means)
  2. Somehow, make sure that the $http request handlers get canceled when the $scope is destroyed.

Clealy, 1. is... scary. 2. is not too painful enough:

$scope.running_http_request = $http.get(...).then(function (result) {...});
$scope.$on("destroy", function () {
  $scope.running_http_request.cancel()
});

(Yes, there is no native cancel method on $http requests but it's not too horrible to add so, I could do this).

Now, all of this is really painful: is there a better way to make sure that if my $scope goes away, whatever was pending with that scope goes away too ?

mathieu
  • 2,954
  • 2
  • 20
  • 31
  • There is an ability to cancel HTTP requests in angular http://stackoverflow.com/a/17328336/2169630 – Kostya Shkryob Aug 19 '16 at 16:03
  • Take a look at [this](http://stackoverflow.com/questions/23244389/angularjs-abort-all-pending-http-requests-on-route-change) question – bumpy Aug 19 '16 at 16:05
  • I would go with the first one, as it seems easier to implement. Not cancelling pending http requests is usually not that big deal. However I would review whether it is a good idea to call `$state.go("^");` on a http success – Tamas Hegedus Aug 19 '16 at 16:09
  • Could you edit your question and put what the end goal/behavior you want? Are you afraid of the request being issued multiple times? or are you looking for the best way to cancel the request? –  Aug 19 '16 at 16:37
  • @bumpy: yes, this is relevant: the httpService they use is what I had in mind and it looks like there is no obvious "better" way. – mathieu Aug 22 '16 at 06:38
  • @TamasHegedus I have loads of code that does this so, writing adhoc code that works for each separate case is tricky: it seems easier/less error-prone to merely cancel the requests – mathieu Aug 22 '16 at 06:40
  • @ProfessorAllman the question title says exactly what I am looking for: I can come up with a myriad of ways to solve my specific bug but I have identified the problem as an instance of the classic "you forgot to cleanup after yourself" so, I am looking for the "best" way to handle pending requests when an async route change happens before the request completes. – mathieu Aug 22 '16 at 06:44

1 Answers1

0

Maybe not the most beautiful way to do this but the following class:

import angular from "angular";

export class HttpManager {
  constructor($scope) {
    let self = this;
    self.pendingRequests = [];
    self.nextRequestId = 0;
    $scope.$on("destroy", function () {
      self.pendingRequests.forEach(function (request) {
        request.canceler.resolve();
      });
      self.requests = [];
    })
  }
  _do(config) {
    let self = this;
    let injector = angular.injector(["ng"])
    let $q = injector.get('$q');
    let $http = injector.get('$http');
    let requestId = self.nextRequestId;
    self.nextRequestId++;
    let canceller = $q.defer();
    let cfg = angular.merge(config, { timeout: canceller.promise });
    self.pendingRequests.push({
      id: requestId,
      canceller: canceller
    });
    let requestPromise = $http(cfg);
    requestPromise.finally(function() {
      self.pendingRequests = self.pendingRequests.filter(request => request.id === requestId);
    });
    return requestPromise;
  }
  post(url, data, config) {
    return this._do(angular.merge({method: "post", url: url, data: data}, config));
  }
};

can be used as follows:

import { HttpManager } from "utils/http-manager.js"
MyController() {
  $scope.http = HttpManager($scope);
  $scope.http.post(...);
}
mathieu
  • 2,954
  • 2
  • 20
  • 31