如何取消$资源请求

Gre*_*lec 27 angularjs angular-resource

我试图找出如何使用$ resource的timeout属性动态取消挂起的请求.理想情况下,我希望能够取消具有某些属性的请求(基于发送的参数),但似乎这可能是不可能的.与此同时,我只是尝试取消所有挂起的请求,然后重置超时承诺以允许新请求.

问题似乎是$ resource配置只允许一个静态的超时值承诺.如果我正在进行单独的$ http调用,那么我可以做到这一点是有道理的,因为我可以为超时传递新的承诺,但是如何才能为$资源工作呢?我在这里设置了一个示例plunker:http://plnkr.co/edit/PP2tqDYXh1NAOU3yqCwP?p = preview

这是我的控制器代码:

app.controller('MainCtrl', function($scope, $timeout, $q, $resource) {
  $scope.canceller = $q.defer();
  $scope.pending = 0;
  $scope.actions = [];
  var API = $resource(
    'index.html', {}, {
      get: {
        method: 'GET',
        timeout: $scope.canceller.promise
      }
    }
  )

  $scope.fetchData = function() {
    if ($scope.pending) {
      $scope.abortPending();
    }
    $scope.pending = 1;
    $scope.actions.push('request');
    API.get({}, function() {
      $scope.actions.push('completed');
      $scope.pending = 0;
    }, function() {
      $scope.actions.push('aborted');
    });
  }

  $scope.abortPending = function() {
    $scope.canceller.resolve();
    $scope.canceller = $q.defer();
  }
});
Run Code Online (Sandbox Code Playgroud)

现在,取消器在有待处理的请求时工作,但我似乎无法重置它 - 一旦一个请求被中止,所有将来的请求也将被中止.

我确定我错过了一些东西,因为能够取消待处理的请求似乎是大多数Web应用程序的一个非常关键的功能(至少我已经构建了).

谢谢

Nik*_* M. 10

回答Gecko IT对我有用,但我必须做一些修改才能:

  • 启用资源ajax调用以多次取消,而无需重新创建资源
  • 使资源向后兼容 - 这意味着除资源工厂之外无需更改任何应用程序(Controller)代码
  • 使代码符合JSLint

这是完整的服务工厂实现(您只需要输入正确的模块名称):

'use strict';

/**
 * ResourceFactory creates cancelable resources.
 * Work based on: https://stackoverflow.com/a/25448672/1677187
 * which is based on: https://developer.rackspace.com/blog/cancelling-ajax-requests-in-angularjs-applications/
 */
/* global array */
angular.module('module_name').factory('ResourceFactory', ['$q', '$resource',
    function($q, $resource) {

        function abortablePromiseWrap(promise, deferred, outstanding) {
            promise.then(function() {
                deferred.resolve.apply(deferred, arguments);
            });

            promise.catch(function() {
                deferred.reject.apply(deferred, arguments);
            });

            /**
             * Remove from the outstanding array
             * on abort when deferred is rejected
             * and/or promise is resolved/rejected.
             */
            deferred.promise.finally(function() {
                array.remove(outstanding, deferred);
            });
            outstanding.push(deferred);
        }

        function createResource(url, options, actions) {
            var resource;
            var outstanding = [];
            actions = actions || {};

            Object.keys(actions).forEach(function(action) {
                var canceller = $q.defer();
                actions[action].timeout = canceller.promise;
                actions[action].Canceller = canceller;
            });

            resource = $resource(url, options, actions);

            Object.keys(actions).forEach(function(action) {
                var method = resource[action];

                resource[action] = function() {
                    var deferred = $q.defer(),
                    promise = method.apply(null, arguments).$promise;

                    abortablePromiseWrap(promise, deferred, outstanding);

                    return {
                        $promise: deferred.promise,

                        abort: function() {
                            deferred.reject('Aborted');
                        },
                        cancel: function() {
                            actions[action].Canceller.resolve('Call cancelled');

                            // Recreate canceler so that request can be executed again
                            var canceller = $q.defer();
                            actions[action].timeout = canceller.promise;
                            actions[action].Canceller = canceller;
                        }
                    };
                };
            });

            /**
             * Abort all the outstanding requests on
             * this $resource. Calls promise.reject() on outstanding [].
             */
            resource.abortAll = function() {
                for (var i = 0; i < outstanding.length; i++) {
                    outstanding[i].reject('Aborted all');
                }
                outstanding = [];
            };

            return resource;
        }

        return {
            createResource: function (url, options, actions) {
                return createResource(url, options, actions);
            }
        };
    }
]);
Run Code Online (Sandbox Code Playgroud)

用法与Gecko IT示例相同.服务工厂:

'use strict';

angular.module('module_name').factory('YourResourceServiceName', ['ResourceFactory', function(ResourceFactory) {
    return ResourceFactory.createResource('some/api/path/:id', { id: '@id' }, {
        create: {
            method: 'POST'
        },
        update: {
            method: 'PUT'
        }
    });
}]);
Run Code Online (Sandbox Code Playgroud)

控制器中的用法(向后兼容):

var result = YourResourceServiceName.create(data);
result.$promise.then(function success(data, responseHeaders) {
    // Successfully obtained data
}, function error(httpResponse) {
    if (httpResponse.status === 0 && httpResponse.data === null) { 
        // Request has been canceled
    } else { 
        // Server error 
    }
});
result.cancel(); // Cancels XHR request
Run Code Online (Sandbox Code Playgroud)

替代方法:

var result = YourResourceServiceName.create(data);
result.$promise.then(function success(data, responseHeaders) {       
    // Successfully obtained data
}).catch(function (httpResponse) {
    if (httpResponse.status === 0 && httpResponse.data === null) { 
        // Request has been canceled
    } else { 
        // Server error 
    }
});
result.cancel(); // Cancels XHR request
Run Code Online (Sandbox Code Playgroud)

进一步改进:

  • 我不喜欢检查请求是否已被取消.更好的方法httpResponse.isCanceled是在取消请求时附加属性,类似于中止.


Hen*_*Jan 0

一种解决方案是每次需要时重新创建资源。

// for canceling an ajax request
$scope.canceler = $q.defer();

// create a resource
// (we have to re-craete it every time because this is the only
// way to renew the promise)
function getAPI(promise) {
    return $resource(
        'index.html', {}, {
            get: {
                method: 'GET',
                timeout: promise
            }
        }
    );
}

$scope.fetchData = function() {

    // abort previous requests if they are still running
    $scope.canceler.resolve();

    // create a new canceler
    $scope.canceler = $q.defer();

    // instead of using "API.get" we use "getAPI().get"
    getAPI( $scope.canceler.promise ).get({}, function() {
        $scope.actions.push('completed');
        $scope.pending = 0;
    }, function() {
        $scope.actions.push('aborted');
    });

}
Run Code Online (Sandbox Code Playgroud)