AngularJSで$ httpリクエストをキャンセルする方法は?


189

AngularJSでAjaxリクエストが与えられた

$http.get("/backend/").success(callback);

別のリクエストが起動された場合にそのリクエストをキャンセルする最も効果的な方法は何ですか(同じバックエンド、異なるパラメーターなど)。


8
以下の回答のいずれも実際にリクエスト自体をキャンセルしません。ブラウザを離れると、HTTPリクエストをキャンセルする方法はありません。以下のすべての答えは、リスナーを何らかの方法で単純に拘束します。HTTPリクエストは引き続きサーバーにヒットし、処理され、サーバーは引き続き応答を送信します。これは、クライアントがまだその応答を要求しているかどうかにすぎません。
Liam

$ httpリクエストがルート変更時に
Code Spy


@リアム私の質問はサーバーでキャンセルされていませんでした。これは、サーバーテクノロジ/実装が何であるかに非常に固有です。私はコールバックの放棄を懸念していました
Sonic Soul

回答:


325

この機能は、タイムアウトパラメータを介して1.1.5リリース追加されました

var canceler = $q.defer();
$http.get('/someUrl', {timeout: canceler.promise}).success(successCallback);
// later...
canceler.resolve();  // Aborts the $http request if it isn't finished.

13
タイムアウトとプロミスによる手動キャンセルの両方が必要な場合はどうすればよいですか?
RamanChodźka13年

15
@RamanChodźkaあなたは約束で両方を行うことができます。タイムアウトを設定して、JavaScriptのネイティブsetTimeout関数またはAngularの$timeoutサービスのいずれかを使用して、一定時間後にプロミスをキャンセルできます。
Quinn Strahl 2013

9
canceler.resolve()は、今後のリクエストをキャンセルします。これはより良いソリューションです:odetocode.com/blogs/scott/archive/2014/04/24/…
ツールキット

7
Ben Nadelのより完全なソリューションのもう1つの良い例:bennadel.com/blog/…
ピート

3
本当に機能しません。実用的なサンプルを提供できますか?
Edward Olamisan 2015年

10

Angular 1.3.15では、タイムアウトプロパティを使用したAngular $ http Ajaxのキャンセルは機能しません。これが修正されるのを待つことができない人のために、AngularでラップされたjQuery Ajaxソリューションを共有しています。

ソリューションには2つのサービスが含まれます。

  • HttpService(jQuery Ajax関数のラッパー);
  • PendingRequestsService(保留中/開いているAjaxリクエストを追跡)

PendingRequestsServiceサービスを次に示します。

    (function (angular) {
    'use strict';
    var app = angular.module('app');
    app.service('PendingRequestsService', ["$log", function ($log) {            
        var $this = this;
        var 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.xhr.abort();
                p.deferred.reject();
            });
            pending.length = 0;
        };
    }]);})(window.angular);

HttpServiceサービス:

     (function (angular) {
        'use strict';
        var app = angular.module('app');
        app.service('HttpService', ['$http', '$q', "$log", 'PendingRequestsService', function ($http, $q, $log, pendingRequests) {
            this.post = function (url, params) {
                var deferred = $q.defer();
                var xhr = $.ASI.callMethod({
                    url: url,
                    data: params,
                    error: function() {
                        $log.log("ajax error");
                    }
                });
                pendingRequests.add({
                    url: url,
                    xhr: xhr,
                    deferred: deferred
                });            
                xhr.done(function (data, textStatus, jqXhr) {                                    
                        deferred.resolve(data);
                    })
                    .fail(function (jqXhr, textStatus, errorThrown) {
                        deferred.reject(errorThrown);
                    }).always(function (dataOrjqXhr, textStatus, jqXhrErrorThrown) {
                        //Once a request has failed or succeeded, remove it from the pending list
                        pendingRequests.remove(url);
                    });
                return deferred.promise;
            }
        }]);
    })(window.angular);

サービスの後半でデータをロードするときに、$ httpの代わりにHttpServiceを使用します。

(function (angular) {

    angular.module('app').service('dataService', ["HttpService", function (httpService) {

        this.getResources = function (params) {

            return httpService.post('/serverMethod', { param: params });

        };
    }]);

})(window.angular);

コードの後半で、データをロードする必要があります。

(function (angular) {

var app = angular.module('app');

app.controller('YourController', ["DataService", "PendingRequestsService", function (httpService, pendingRequestsService) {

    dataService
    .getResources(params)
    .then(function (data) {    
    // do stuff    
    });    

    ...

    // later that day cancel requests    
    pendingRequestsService.cancelAll();
}]);

})(window.angular);

9

で発行されたリクエストのキャンセルは$http、AngularJSの現在のバージョンではサポートされていません。この機能を追加するためにプル要求が開かれていますが、このPRはまだレビューされていないため、AngularJSコアに組み込むかどうかは不明です。


そのPRは拒否され、OPは更新されたものをここに提出しましたgithub.com/angular/angular.js/pull/1836
Mark Nadig

そして、それも閉じられました。
frapontillo 2013

それのバージョンはこれとして上陸した。まだ、最終バージョンを使用するための構文を理解しようとしています。PRに使用例が付属していることを願っています。:)
SimplGy 2014

「使用法」の角度のドキュメントページdocs.angularjs.org/api/ng/service/$httpは、タイムアウト設定を説明し、どのオブジェクト(Promise)が受け入れられるかについても説明しています。
Igor Lino 2016

6

ui-routerを使用してstateChangeStartで保留中のリクエストをキャンセルする場合は、次のようなものを使用できます。

//稼働中

                var deferred = $q.defer();
                var scope = this;
                $http.get(URL, {timeout : deferred.promise, cancel : deferred}).success(function(data){
                    //do something
                    deferred.resolve(dataUsage);
                }).error(function(){
                    deferred.reject();
                });
                return deferred.promise;

// UIrouter設定内

$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
    //To cancel pending request when change state
       angular.forEach($http.pendingRequests, function(request) {
          if (request.cancel && request.timeout) {
             request.cancel.resolve();
          }
       });
    });

これは私にとって
うまくいき

UIルーター構成request.timeoutが存在するかどうかを知る必要があるのはなぜですか?
トリシス2017

6

何らかの理由でconfig.timeoutが機能しません。私はこのアプローチを使用しました:

let cancelRequest = $q.defer();
let cancelPromise = cancelRequest.promise;

let httpPromise = $http.get(...);

$q.race({ cancelPromise, httpPromise })
    .then(function (result) {
...
});

キャンセルするには、cancelRequest.resolve()を使用します。実際にはリクエストはキャンセルされませんが、少なくとも不要な応答はありません。

お役に立てれば。


SyntaxErrorを見ました{ cancelPromise, httpPromise }か?
Mephiztopheles 2016

これはES6の構文です。{c:cancelPromise、h:httpPromise}を試すことができます
Aliaksandr Hmyrak

なるほど、オブジェクトショートイニシャライザ
Mephiztopheles

3

これにより、次のように$ httpサービスをabortメソッドで装飾することで、受け入れられる回答が強化されます...

'use strict';
angular.module('admin')
  .config(["$provide", function ($provide) {

$provide.decorator('$http', ["$delegate", "$q", function ($delegate, $q) {
  var getFn = $delegate.get;
  var cancelerMap = {};

  function getCancelerKey(method, url) {
    var formattedMethod = method.toLowerCase();
    var formattedUrl = encodeURI(url).toLowerCase().split("?")[0];
    return formattedMethod + "~" + formattedUrl;
  }

  $delegate.get = function () {
    var cancelerKey, canceler, method;
    var args = [].slice.call(arguments);
    var url = args[0];
    var config = args[1] || {};
    if (config.timeout == null) {
      method = "GET";
      cancelerKey = getCancelerKey(method, url);
      canceler = $q.defer();
      cancelerMap[cancelerKey] = canceler;
      config.timeout = canceler.promise;
      args[1] = config;
    }
    return getFn.apply(null, args);
  };

  $delegate.abort = function (request) {
    console.log("aborting");
    var cancelerKey, canceler;
    cancelerKey = getCancelerKey(request.method, request.url);
    canceler = cancelerMap[cancelerKey];

    if (canceler != null) {
      console.log("aborting", cancelerKey);

      if (request.timeout != null && typeof request.timeout !== "number") {

        canceler.resolve();
        delete cancelerMap[cancelerKey];
      }
    }
  };

  return $delegate;
}]);
  }]);

このコードは何をしていますか?

リクエストをキャンセルするには、「約束」タイムアウトを設定する必要があります。HTTPリクエストにタイムアウトが設定されていない場合、コードは「約束」タイムアウトを追加します。(タイムアウトがすでに設定されている場合、何も変更されません)。

ただし、約束を解決するには、「据え置き」のハンドルが必要です。したがって、後で「据え置き」を取得できるようにマップを使用します。abortメソッドを呼び出すと、マップから「据え置き」が取得され、次にresolveメソッドを呼び出してhttpリクエストをキャンセルします。

これが誰かを助けることを願っています。

制限

現在、これは$ http.getでのみ機能しますが、$ http.postなどのコードを追加できます。

使い方 ...

その後、次のように、たとえば状態の変化時にそれを使用できます...

rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
  angular.forEach($http.pendingRequests, function (request) {
        $http.abort(request);
    });
  });

いくつかのHTTPリクエストを同時に起動するアプリを作成していますが、それらすべてを手動で中止する必要があります。私はあなたのコードを試しましたが、それは最後のリクエストを中止するだけです。以前にそれはあなたに起こりましたか?任意の助けいただければ幸いです。
Miguel Trabajo 2016年

1
ここのコードは、延期オブジェクトへの参照のルックアップを維持し、延期オブジェクトはアボートを実行する必要があるため、後で取得できるようにします。ルックアップで重要なのは、キーと値のペアです。値は据え置きオブジェクトです。キーは、リクエストメソッド/ URLに基​​づいて生成された文字列です。同じメソッド/ URLへの複数のリクエストを中止していると思います。このため、すべてのキーは同一であり、マップ内で互いに上書きされます。url /メソッドが同じでも一意のものが生成されるように、キー生成ロジックを微調整する必要があります。
danday74 2016年

1
上から続けます...これはコードのバグではなく、コードは複数のリクエストの中止を処理します...しかし、コードは同じhttpメソッドを使用して同じURLへの複数のリクエストの中止を処理することを意図したものではありません...ロジックを調整すると、かなり簡単に機能させることができるはずです。
danday74 2016年

1
どうもありがとうございました!私は同じURLに複数のリクエストを行っていましたが、パラメーターが異なりました。あなたが言った後、その行を変更しましたが、それは魅力のように機能しました!
Miguel Trabajo 2016年

1

これは、複数のリクエストを処理するバージョンです。エラーブロックのエラーを抑制するために、コールバックでキャンセルされたステータスもチェックします。(Typescript)

コントローラレベル:

    requests = new Map<string, ng.IDeferred<{}>>();

私のhttpで:

    getSomething(): void {
        let url = '/api/someaction';
        this.cancel(url); // cancel if this url is in progress

        var req = this.$q.defer();
        this.requests.set(url, req);
        let config: ng.IRequestShortcutConfig = {
            params: { id: someId}
            , timeout: req.promise   // <--- promise to trigger cancellation
        };

        this.$http.post(url, this.getPayload(), config).then(
            promiseValue => this.updateEditor(promiseValue.data as IEditor),
            reason => {
                // if legitimate exception, show error in UI
                if (!this.isCancelled(req)) {
                    this.showError(url, reason)
                }
            },
        ).finally(() => { });
    }

ヘルパーメソッド

    cancel(url: string) {
        this.requests.forEach((req,key) => {
            if (key == url)
                req.resolve('cancelled');
        });
        this.requests.delete(url);
    }

    isCancelled(req: ng.IDeferred<{}>) {
        var p = req.promise as any; // as any because typings are missing $$state
        return p.$$state && p.$$state.value == 'cancelled';
    }

ネットワークタブを見ると、見事に機能していることがわかります。私はメソッドを4回呼び出しましたが、最後のものだけが通過しました。

ここに画像の説明を入力してください


req.resolve( 'キャンセル'); 1.7.2バージョンを使用しています。それが再び呼び出され、最初の呼び出しがまだ保留状態にある場合でも、呼び出しをキャンセルしたいです。助けてください。同じURLの保留中のすべてのAPIをキャンセルして、新しく呼び出された通話データを常に提供したい
Sudarshan Kalebere

1

追加する$http「デコレータ」を使用してサービスにカスタム関数を追加できますabort()ます。

ここにいくつかの作業コードがあります:

app.config(function($provide) {
    $provide.decorator('$http', function $logDecorator($delegate, $q) {
        $delegate.with_abort = function(options) {
            let abort_defer = $q.defer();
            let new_options = angular.copy(options);
            new_options.timeout = abort_defer.promise;
            let do_throw_error = false;

            let http_promise = $delegate(new_options).then(
                response => response, 
                error => {
                    if(do_throw_error) return $q.reject(error);
                    return $q(() => null); // prevent promise chain propagation
                });

            let real_then = http_promise.then;
            let then_function = function () { 
                return mod_promise(real_then.apply(this, arguments)); 
            };

            function mod_promise(promise) {
                promise.then = then_function;
                promise.abort = (do_throw_error_param = false) => {
                    do_throw_error = do_throw_error_param;
                    abort_defer.resolve();
                };
                return promise;
            }

            return mod_promise(http_promise);
        }

        return $delegate;
    });
});

このコードは、angularjsのデコレーター機能を使用with_abort()して、$httpサービスに関数を追加します。

with_abort() 使用する $httpは、HTTPリクエストを中止することを可能にするオプションのタイムアウト。

返されたpromiseは、abort()関数を含むように変更されています。また、これは、abort() promiseをチェーンした場合でも機能。

次に、使用方法の例を示します。

// your original code
$http({ method: 'GET', url: '/names' }).then(names => {
    do_something(names));
});

// new code with ability to abort
var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
    function(names) {
        do_something(names));
    });

promise.abort(); // if you want to abort

デフォルトでは、 abort()、リクエストとキャンセルされ、promiseハンドラーは実行されません。

エラーハンドラーを呼び出す場合は、trueを abort(true)

エラーハンドラーで、xhrStatusプロパティを確認することにより、「エラー」が「中止」が原因であったかどうかを確認できます。次に例を示します。

var promise = $http.with_abort({ method: 'GET', url: '/names' }).then(
    function(names) {
        do_something(names));
    }, 
    function(error) {
        if (er.xhrStatus === "abort") return;
    });
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.