2012-12-18 8 views
162

eine Ajax-Anfrage in AngularJS Bei

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

, was der effektivste Weg ist, diesen Antrag zu stornieren, wenn eine weitere Anforderung (gleiche Backend, verschiedene Parameter zum Beispiel) gestartet wird.

+5

Keine der folgenden Antworten löscht die Anfrage selbst. Es gibt keine Möglichkeit, eine HTTP-Anfrage abzubrechen, sobald sie den Browser verlässt. Alle folgenden Antworten bleiben dem Zuhörer einfach in irgendeiner Weise überlassen. Die HTTP-Anfrage trifft immer noch den Server, wird noch verarbeitet und der Server sendet immer noch eine Antwort, es ist nur ein Fall davon, ob der Client immer noch auf diese Antwort wartet oder nicht. – Liam

+0

$ http Anfrage stornieren auf Route ändern http://www.freakyjolly.com/how-to-cancel-http-requests-in-angularjs-app/ –

Antwort

283

war diese Funktion added to the 1.1.5 release über einen Timeout-Parameter:

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

Was soll ich tun, wenn ich sowohl eine Zeitüberschreitung als auch eine manuelle Stornierung über das Versprechen benötige? –

+12

@ RamanChodźka Sie können beide mit einem Versprechen tun; Sie können ein Zeitlimit setzen, um das Versprechen nach einiger Zeit abzubrechen, entweder mit der systemeigenen 'setTimeout'-Funktion von JavaScript oder dem' $ timeout'-Service von Angular. –

+0

Vielen Dank! Meine vorherige Lösung war nur jQuery für Ajax-Anfragen zu verwenden, und das wollte ich wirklich vermeiden. – mirichan

9

Die Stornierung von Anfragen mit $http wird von der aktuellen Version von AngularJS nicht unterstützt. Es gibt eine pull request opened, um diese Fähigkeit hinzuzufügen, aber diese PR wurde noch nicht überprüft, so ist es nicht klar, ob es in AngularJS Kern machen wird.

+0

, dass PR abgelehnt wurde, eingereicht OP aktualisiert hier https: // github.com/angular/angular.js/pull/1836 –

+0

Und das war auch geschlossen. – frapontillo

+0

Eine Version davon [gelandet] (https://github.com/angular/angular.js/commit/9f4f5937112655a9881d3281da8e72035bc8b180). Ich versuche immer noch, die Syntax für die endgültige Version herauszufinden. Wünschte, dass die PRs mit Verwendungsbeispielen kamen! :) – SimplGy

9

Angular $ http Ajax mit der Zeit Eigenschaft Cancelling nicht in Angular 1.3.15 funktioniert. Für diejenigen, die nicht darauf warten können, dass dies behoben wird, teile ich eine jQuery Ajax Lösung in Angular verpackt.

Die Lösung beinhaltet zwei Dienste an:

  • Httpservice (ein Wrapper um die Funktion jQuery Ajax);
  • PendingRequestsService (Verfolgt die anstehenden/offenen Anfragen Ajax)

Hier die PendingRequestsService Service geht:

(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); 

Der Httpservice-Service:

 (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); 

Später in Ihrem Dienst, wenn Sie sind Laden von Daten würden Sie den HttpService anstelle von $ http:

verwenden
(function (angular) { 

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

     this.getResources = function (params) { 

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

     }; 
    }]); 

})(window.angular); 

später im Code möchten Sie die Daten laden:

(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); 
6

Wenn Sie mit ui-Router ausstehenden Anforderungen auf stateChangeStart abbrechen möchten, können Sie so etwas wie dieses verwenden können:

// in Service

   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; 

// in UIrouter Config

$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(); 
      } 
     }); 
    }); 
+0

Dies funktionierte für mich - sehr einfach und ich fügte einen anderen hinzu, um den Anruf zu benennen, also kann ich den Anruf auswählen und nur einige der Anrufe abbrechen –

+0

Warum muss die UI Router-Konfiguration wissen, ob 'request.timeout' vorhanden ist? – trysis

2

Dies erhöht wie folgt den $ HTTP-Dienstes mit einer Abort-Methode durch die Dekoration die akzeptierte Antwort ...

'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; 
}]); 
    }]); 

WAS TUT DIESER CODE?

Um eine Anforderung abzubrechen, muss ein "Versprechen" Timeout gesetzt werden. Wenn für die HTTP-Anforderung kein Zeitlimit festgelegt ist, fügt der Code ein Zeitlimit für "Versprechen" hinzu. (Wenn ein Timeout bereits eingestellt ist, wird nichts geändert).

Um jedoch das Versprechen zu lösen, benötigen wir einen Griff auf die "zurückgestellt". Wir verwenden also eine Karte, so dass wir die "zurückgestellte" später abrufen können. Wenn wir die Methode abort aufrufen, wird die "zurückgestellte" aus der Karte abgerufen und dann rufen wir die resolve-Methode auf, um die http-Anfrage abzubrechen.

Ich hoffe, das hilft jemandem.

GRENZEN

Derzeit funktioniert dies nur für $ http.get aber Sie können Code für $ http.post hinzufügen und so weiter

WIE IST ...

Sie können dann verwenden, B. auf Zustandswechsel, wie folgt ...

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

Ich mache eine App, die einige HTTP-Anfragen zur gleichen Zeit auslöst und ich muss sie alle manuell abbrechen. Ich habe Ihren Code ausprobiert, aber er bricht nur die letzte Anfrage ab. Ist dir das schon einmal passiert? Jede Hilfe wäre willkommen. –

+1

Der Code behält hier eine Suche nach Verweisen auf die Verzögerungsobjekte bei, so dass sie später abgerufen werden können, da das Verzögerungsobjekt einen Abbruch durchführen muss. Das Wichtigste bei der Suche ist das Schlüssel-Wert-Paar. Der Wert ist das Verzögerungsobjekt. Der Schlüssel ist eine Zeichenkette, die basierend auf der Anfrage-Methode/URL erzeugt wird. Ich vermute, dass Sie mehrere Anfragen an dieselbe Methode/URL abbrechen. Daher sind alle Schlüssel identisch und sie überschreiben sich gegenseitig in der Karte. Sie müssen die Schlüsselgenerierungslogik so optimieren, dass eine eindeutige generiert wird, selbst wenn die URL/Methode identisch ist. – danday74

+1

Fortsetzung von oben ... das ist kein Fehler im Code, der Code behandelt das Abbrechen mehrerer Anfragen ... aber der Code war einfach nie dazu gedacht, mehrere Anfragen an dieselbe URL mit derselben HTTP-Methode abzubrechen ... Aber wenn Sie die Logik optimieren, sollten Sie in der Lage sein, es ziemlich einfach zu funktionieren. – danday74

5

Aus irgendeinem Grund funktioniert config.timeout nicht für mich. Ich habe diesen Ansatz:

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

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

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

Und cancelRequest.resolve() abzubrechen. Tatsächlich wird eine Anfrage nicht abgebrochen, aber Sie erhalten keine unnötige Antwort.

Hoffe, das hilft.

+0

Haben Sie Ihren SyntaxError '{cancelPromise, httpPromise}'? – Mephiztopheles

+0

Dies ist ES6-Syntax, Sie können versuchen {c: cancelPromise, h: httpPromise} –

+0

Ich sehe, Objekt shortinitializer – Mephiztopheles

0

Hier ist eine Version, die mehrere Anfragen behandelt, prüft auch auf abgebrochenen Status im Rückruf, um Fehler in Fehlerblock zu unterdrücken. (In Maschinenschrift)

Controller-Ebene:

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

in meinem http erhalten:

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(() => { }); 
    } 

Hilfsmethoden

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'; 
    } 

jetzt auf der Registerkarte Netzwerk suchen, sehe ich, dass es arbeitet wunderschön. Ich habe die Methode 4 mal aufgerufen und nur die letzte ging durch.

enter image description here