【问题标题】:AngularJS abort all pending $http requests on route changeAngularJS 在路由更改时中止所有挂起的 $http 请求
【发布时间】:2014-06-08 06:59:52
【问题描述】:

请先过一遍代码

app.js

var app = angular.module('Nimbus', ['ngRoute']);

route.js

app.config(function($routeProvider) {
    $routeProvider
    .when('/login', {
        controller: 'LoginController',
        templateUrl: 'templates/pages/login.html',
        title: 'Login'
    })
    .when('/home', {
        controller: 'HomeController',
        templateUrl: 'templates/pages/home.html',
        title: 'Dashboard'
    })
    .when('/stats', {
        controller: 'StatsController',
        templateUrl: 'templates/pages/stats.html',
        title: 'Stats'
    })
}).run( function($q, $rootScope, $location, $route, Auth) {
    $rootScope.$on( "$routeChangeStart", function(event, next, current) {
        console.log("Started");


        /* this line not working */
        var canceler = $q.defer();
        canceler.resolve();

    });

    $rootScope.$on("$routeChangeSuccess", function(currentRoute, previousRoute){
        $rootScope.title = ($route.current.title) ? $route.current.title : 'Welcome';
    });
 })

home-controller.js

app.controller('HomeController',
    function HomeController($scope, API) {
        API.all(function(response){
            console.log(response);
        })
    }
)

stats-controller.js

app.controller('StatsController',
    function StatsController($scope, API) {
        API.all(function(response){
            console.log(response);
        })
    }
)

api.js

app.factory('API', ['$q','$http', function($q, $http) {    
    return {
        all: function(callback) {
            var canceler = $q.defer();
            var apiurl = 'some_url'
            $http.get(apiurl,{timeout: canceler.promise}).success(callback);
        }
    }
}]);

当我从家搬到 stats 时,API 会再次发送 http 请求,我有很多这样的 http 调用,我只粘贴了几行代码。

我需要的是我需要cancel在 routechangestart 或成功时中止所有挂起的 http 请求

或者任何其他方式来实现相同的?

【问题讨论】:

  • 当您说待处理请求时,您的意思是已经发送到服务器但您没有得到响应的请求?还是排队的(由于单个域名的并发http请求限制)?
  • 但是http调用的方式不同,我认为我们可以在这里应用不同

标签: javascript angularjs


【解决方案1】:

我认为这是中止请求的最佳解决方案。它使用拦截器和 $routeChangeSuccess 事件。 http://blog.xebia.com/cancelling-http-requests-for-fun-and-profit/

【讨论】:

  • 我遇到的最佳解决方案,也适用于 $resource,因为它包装了 $http。
【解决方案2】:

您可以使用$http.pendingRequests 来执行此操作。

首先,当您提出请求时,请执行以下操作:

var cancel = $q.defer();
var request = {
    method: method,
    url: requestUrl,
    data: data,
    timeout: cancel.promise, // cancel promise, standard thing in $http request
    cancel: cancel // this is where we do our magic
};

$http(request).then(.....);

现在,我们取消$routeChangeStart 中的所有待处理请求

$rootScope.$on('$routeChangeStart', function (event, next, current) {

    $http.pendingRequests.forEach(function(request) {
        if (request.cancel) {
            request.cancel.resolve();
        }
    });
});

通过这种方式,您还可以通过在请求中不提供“取消”字段来“保护”某些请求不被取消。

【讨论】:

  • 这会得到一个指向 promise 底层的延迟对象的句柄并过早地解决它。这与“取消” AJAX 请求本身不同,后者仍将在服务器上处理并返回。这不仅会过早地触发 promise 上的所有 .then() 处理程序(可能会导致空异常,因为他们期待响应对象),我还希望它在实际的 AJAX 回调不可避免地尝试时抛出双分辨率异常再次解决?
  • Eric,我对此进行了测试,它可以工作。如果您查看文档 (docs.angularjs.org/api/ng/service/$http),它指出请求的“超时”属性可以是一个承诺(超时 - {number|Promise} - 以毫秒为单位的超时,或者在解决时应该中止请求的承诺。) .属性 'cancel' 只是给了我们自己做的权力,所以当我们自己解决它时,请求就像它达到了超时一样。
  • 另外,如果您在这里查看大多数 +1 的答案,它的作用完全一样,但结构更好,因为它包含在服务中。
  • 好吧,我没有意识到底层实现是如何工作的。为最小的工作解决方案添加 +1。
  • 太棒了,如果您将自己的拦截器添加到 $httpProvider.interceptors 中,即使使用 $resource 也可以工作,该拦截器将必填字段设置为传出请求。
【解决方案3】:

我为此整理了一些概念性代码。它可能需要调整以满足您的需求。有一个 pendingRequests 服务具有用于添加、获取和取消请求的 API,还有一个 httpService 包装 $http 并确保跟踪所有请求。

通过利用 $http 配置对象 (docs),我们可以获得取消待处理请求的方法。

我已经创建了一个 plnkr,但是您需要快速查看请求被取消,因为我发现的测试站点通常会在半秒内响应,但是您会在 devtools 网络选项卡中看到请求执行被取消。在您的情况下,您显然会触发来自$routeProvider 的适当事件的cancelAll() 调用。

控制器只是为了演示这个概念。

DEMO

angular.module('app', [])
// This service keeps track of pending requests
.service('pendingRequests', function() {
  var pending = [];
  this.get = function() {
    return pending;
  };
  this.add = function(request) {
    pending.push(request);
  };
  this.remove = function(request) {
    pending = _.filter(pending, function(p) {
      return p.url !== request;
    });
  };
  this.cancelAll = function() {
    angular.forEach(pending, function(p) {
      p.canceller.resolve();
    });
    pending.length = 0;
  };
})
// This service wraps $http to make sure pending requests are tracked 
.service('httpService', ['$http', '$q', 'pendingRequests', function($http, $q, pendingRequests) {
  this.get = function(url) {
    var canceller = $q.defer();
    pendingRequests.add({
      url: url,
      canceller: canceller
    });
    //Request gets cancelled if the timeout-promise is resolved
    var requestPromise = $http.get(url, { timeout: canceller.promise });
    //Once a request has failed or succeeded, remove it from the pending list
    requestPromise.finally(function() {
      pendingRequests.remove(url);
    });
    return requestPromise;
  }
}])
// The controller just helps generate requests and keep a visual track of pending ones
.controller('AppCtrl', ['$scope', 'httpService', 'pendingRequests', function($scope, httpService, pendingRequests) {
  $scope.requests = [];
  $scope.$watch(function() {
    return pendingRequests.get();
  }, function(pending) {
    $scope.requests = pending;
  })

  var counter = 1;
  $scope.addRequests = function() {
    for (var i = 0, l = 9; i < l; i++) {
      httpService.get('https://public.opencpu.org/ocpu/library/?foo=' + counter++);  
    }
  };
  $scope.cancelAll = function() {
    pendingRequests.cancelAll();
  }
}]);

【讨论】:

  • @jmb.mage 我们的 http-wrapper 做了类似的事情,除了它暴露了getpostput 等,并委托给一个利用$http 配置的内部方法-目的。我只是想将所有这些添加到我的示例中将是噪音,因为上下文是如何中止请求:) 不要忘记将 HTTP 动词添加到待处理列表中的键中,对于 POST 和 @ 987654337@ 也是数据的字符串化版本。
  • 设置与stackoverflow.com/questions/22090792/…类似,只是不处理取消请求,而是防止重复请求。
  • 感谢您的支持 - 我很惊讶这对于更多建造水疗中心的人来说不是必需品。尽管我认为最好的解决方案是通过自定义服务将请求堆叠起来,但您的代码将我推向了这个方向。
  • @SrikanthKondaparthy 我想您可以进一步调整它以添加超时,但我不再使用角度,所以我不知道是否有任何可以使用的核心功能。天真的解决方案是在 30 秒后使用 setTimeoutresolve() 取消器(如果成功,请记住 clearTimeout,但可能有更好的解决方案。我个人认为,如果请求需要 30 秒,那么主要问题出在服务器端:)
【解决方案4】:

请注意,我是 Angular 的新手,所以这可能不是最佳选择。 另一种解决方案可能是: 在 $http 请求中添加“超时”参数,Docs 我是这样做的:

在我调用所有 Rest 服务的工厂中,有这个逻辑。

module.factory('myactory', ['$http', '$q', function ($http, $q) {
    var canceler = $q.defer();

    var urlBase = '/api/blabla';
    var factory = {};

    factory.CANCEL_REQUESTS = function () {
        canceler.resolve();
        this.ENABLE_REQUESTS();
    };
    factory.ENABLE_REQUESTS = function () {
        canceler = $q.defer();
    };
    factory.myMethod = function () {
        return $http.get(urlBase, {timeout: canceler.promise});
    };
    factory.myOtherMethod= function () {
        return $http.post(urlBase, {a:a, b:b}, {timeout: canceler.promise});
    };
    return factory;
}]);

在我的角度应用程序配置上:

return angular.module('app', ['ngRoute', 'ngSanitize', 'app.controllers', 'app.factories',
    'app.filters', 'app.directives', 'ui.bootstrap', 'ngGeolocation', 'ui.select' ])
.run(['$location', '$rootScope', 'myFactory', function($location, $rootScope, myFactory) {
    $rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
        myFactory.CANCEL_REQUESTS();
        $rootScope.title = current.$$route.title;
    });
}]);

这样,它会捕获所有“路由”更改并停止使用该“计时器”配置的所有请求,因此您可以选择对您至关重要的内容。

我希望它对某人有所帮助。 问候

【讨论】:

    猜你喜欢
    • 2018-02-19
    • 2017-12-19
    • 2018-12-28
    • 2021-05-19
    • 1970-01-01
    • 2014-01-24
    • 2014-06-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多