2012-12-18 124 views
162

鑑於AngularJS

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

是什麼,如果另一個請求啓動(相同的後端,比如不同的參數)以取消該請求的最有效的方式Ajax請求。

+5

下面的答案都沒有實際取消請求本身。一旦HTTP請求離開瀏覽器,就無法取消它。下面的所有答案都以某種方式簡單地告訴了聽衆。 HTTP請求仍然會觸發服務器,仍然處理完畢,服務器仍然會發送響應,這只是客戶端仍在響應或不響應的一個案例。 – Liam

+0

$ http請求取消路由更改http://www.freakyjolly.com/how-to-cancel-http-requests-in-angularjs-app/ –

回答

283

此功能是通過一個超時參數added to the 1.1.5 release

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

如果我需要通過承諾暫停和手動取消,我應該怎麼做? –

+12

@RamanChodźka你可以做一個承諾,您可以設置一個超時時間,以便在一段時間後取消承諾,無論是使用JavaScript的本地'setTimeout'函數還是使用Angular的'$ timeout'服務。 –

+0

非常感謝!我之前的解決方案只是使用jQuery來處理Ajax請求,而我真的很想避免這種情況。 – mirichan

9

當前版本的AngularJS不支持取消$http發出的請求。有一個pull request opened來增加這個功能,但是這個PR還沒有被審查,所以目前還不清楚它是否會把它變成AngularJS的核心。

+0

PR被拒絕,OP提交更新一個在這裏 https:// github.com/angular/angular.js/pull/1836 –

+0

而且這也被關閉了。 – frapontillo

+0

它的一個版本[https://github.com/angular/angular.js/commit/9f4f5937112655a9881d3281da8e72035bc8b180]。仍試圖找出使用最終版本的語法。祝願PR攜帶使用樣品! :) – SimplGy

9

取消角$ HTTP阿賈克斯與超時財產角1.3.15不起作用。 對於那些不能等待修復的人,我分享了一個包裝在Angular中的jQuery Ajax解決方案。

該解決方案包括兩種服務:

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

在服務以後,當你加載數據你會使用HttpService而不是$ http:

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

如果要取消等待與UI的路由器上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(); 
      } 
     }); 
    }); 
+0

這對我很有幫助 - 非常簡單,我添加了另一個名稱的呼叫,所以我可以選擇呼叫,只取消一些呼叫 –

+0

爲什麼UI路由器配置需要知道'request.timeout'是否存在? – trysis

2

這增強用如下的方法中止裝飾$ http服務的接受的答案...

'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方法時,從地圖中檢索「deferred」,然後調用resolve方法取消http請求。

希望這可以幫助別人。

限制

目前只爲$作品http.get但你可以爲$ http.post等

如何使用......

然後,您可以使用它添加代碼,例如,在狀態變化,如下...

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

我正在製作一個應用程序,它可以同時觸發一些http請求,我需要手動將它們全部中止。我試過你的代碼,但它只會中止最後的請求。那之前是否發生過這種事?任何幫助,將不勝感激。 –

+1

此處的代碼維護對延遲對象的引用查找,以便稍後可以檢索它們,因爲要求延遲對象執行中止。查找重要的是關鍵:值對。該值是延遲對象。關鍵是基於請求方法/ url生成的字符串。我猜你正在中止多個請求到相同的方法/網址。因爲所有的鍵都是相同的,所以它們在地圖上相互覆蓋。您需要調整密鑰生成邏輯,以便即使URL /方法相同也會生成唯一的邏輯。 – danday74

+1

繼續從上面...這不是在代碼中的錯誤,代碼處理中止多個請求...但代碼根本就沒有打算處理使用相同的http方法中止多個請求到同一個url ...但如果你調整邏輯,你應該能夠相當容易地運作。 – danday74

5

由於某種原因config.timeout不適合我。我用這個方法:

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

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

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

而且cancelRequest.resolve()取消。其實它並不是取消一個請求,但你至少沒有得到不必要的迴應。

希望這會有所幫助。

+0

您是否看到過SyntaxError'{cancelPromise,httpPromise}'? – Mephiztopheles

+0

這是ES6語法,你可以嘗試{c:cancelPromise,h:httpPromise} –

+0

我看到,對象短初始化程序 – Mephiztopheles

0

這是一個處理多個請求的版本,它還檢查回調中的取消狀態以抑制錯誤塊中的錯誤。 (以打字稿)

控制器級別:

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次,只有最後一次經歷了。

enter image description here