pendingRequests
AngularJSの$http
にはpendingRequests
というfunctionがあり、これを利用すると既に送ったHTTPリクエストでまだ結果が返ってきてないリクエストを知ることができるようです。
https://code.angularjs.org/1.2.28/docs/api/ng/service/$http #pendingRequests
主にデバッグの目的で使用されると書いてあるのですが、これとInterceptor
を組み合わせることでPOSTやPUT,DELETEに対する二重サブミットを防止したいと思います。
と、思ったのですが、既に以下のブログで紹介されておりました。
http://ussy00.hatenablog.com/entry/2014/05/28/192819
ただ、上記のブログでは$http
でリクエスト送信を行った場合が紹介されていますので、
本稿では$resource
でリクエスト送信を行った場合を紹介します。
Interceptorの基本的な利用方法は以下を参照してください。
AngularJSのInterceptorを利用する
Interceptorを作成します。
angular.module('myapp.configurations', ['myapp.controllers'])
.config(function($httpProvider) {
$httpProvider.interceptors.push(
function($injector, $rootScope, $q) {
return {
request : function(config) {
var $http = $injector.get('$http');
var requestToken = config.headers['X-Request-Token'];
function checkIfDuplicated(config) {
var duplicated = $http.pendingRequests.filter(
function(pendingConfig) {
return pendingConfig.headers['X-Request-Token']
&& pendingConfig.headers['X-Request-Token'] === requestToken;
}
);
return duplicated.length > 0;
}
if (requestToken && checkIfDuplicated(config)) {
return $q.defer().promise;
}
return config || $q.when(config);
}
};
}
);
});
次に、HTTPリクエストを送る$resource
を以下のようにします。
$resource(url, {}, {
saveWithCheck: {method:'POST', headers:{'X-Request-Token':'hogehoge'}}
});
解説
Interceptorでリクエストconfigのheader情報からリクエストを特定するトークンを取り出します。
これを利用し、pendingRequestsの配列要素一つ一つに対して付き合わせることで二重サブミットを回避する仕組みです。
headerにトークン情報を設定しているのはさらにサーバーサイドでのトークンチェックを行いたいからですが、ここでは省略します。
リクエスト送信元からInterceptorまでのパラメータのやり取りは、正直どんなやり方でも良いのですが、
$resource
は$http
と違ってconfigの属性を増やしたりすることができません。
そこで、$resource
のactionsを利用してheaderにパラメータを設定することにしています。
(see https://docs.angularjs.org/api/ngResource/service/$resource #Usage)
これにより、
二重サブミットを防止したいリクエストの場合は、saveWithCheck
を呼び出し、
そうでないリクエストの場合は、$resouce
デフォルトのsave
を呼び出すことで使い分けることができるようになりました。
注意
余談ですが、$resource
のactionsに設定できるparamsに注意してください。
当たり前かもしれませんが、paramsにパラメータを設定すると、POSTリクエストなのにそれらのパラメータがURLの後ろにクエリストリングとなって送信されてしまいます。
当初リクエスト送信元からInterceptorまでのパラメータのやり取りをparamsに入れていたのですが、送信したURLをみるとそうなっていたので焦りました。