こんにちは、担当しているWebアプリ開発案件も佳境にはいり若干疲弊気味のフロントエンドエンジニアのいなばです (レッドブルほしい)。
jQuery.ajaxを使用しないケース(SPAなど)で頻繁にAjax通信を行う際、通信エラー時の制御を、リクエストを飛ばす箇所ごとに記述するのは面倒ですよね。 そこで今回は、Ajax通信のエラー時の制御を一括で制御する仕組み(HttpInterceptor)が欲しいなと思って調べてみました。
ちなみにjQuery.ajaxの場合は、仕組みが用意されているのでとても簡単です。
http://stackoverflow.com/questions/36549308/global-ajaxerror-event-before-local-error-event
HttpInterceptorとは
Angular1.x系では、「HttpInterceptor」という、Ajax通信を横取りしてリクエストやレスポンスデータの整形・エラーハンドリングの制御を差し込んだりする機能が用意されており、 それを使うことで通信処理とエラーハンドリングを分離することができました。
- 参考
- Angular1.x系のHttpInterceptorについては、下記のページを1/4ほど読み進めた位置にある「Interceptors」の項目を読むと良いです。
「AngularJS: $http」
https://docs.angularjs.org/api/ng/service/$httpAngular2.x系ではフレームワーク側で仕組みが用意されていませんが、自前で用意することで同様のことができます。
「Angular2でHTTP Interceptorを使いたい」
http://blog.mitsuruog.info/2016/04/way-of-http-interceptpr-in-angular2.html
fetchやsuperagentなどでのHttpInterceptor
参考記事にあげた「Angular2でHTTP Interceptorを使いたい」にも書いてあるとおり、ラッパーを作ってあげることで ajax通信する機能を実現できるようです。
今回は、fetchを使うにあたり、ffetchというfetchのラッパーが便利そうだったので、これを拡張して実装します。 superagentを使う場合でも、実装の考え方はほとんど同じです。
各メソッドのcatchで共通エラーハンドリングのための関数を挟み、RxJSのSubjectにエラーを渡しています。 エラーを検知したい箇所でこのSubjectをsubscribeすれば、そこに自動的にエラーが流れてくるようにしてみました。
fetchのラッパーをさらにラップして共通エラーハンドリングできるようにしたコード
import { Observable } from "rxjs/Observable";
import { Subject } from 'rxjs/Subject';
import { FFetch } from 'ffetch';
function checkStatus(res) {
if (!res.ok) {
return res.json()
.then(err => Promise.reject(err.error));
}
return res;
}
export class CustomFFetch extends FFetch {
constructor(...arg) {
super(...arg);
this.errorEvents = new Subject();
}
get(url, options) {
return super.get(url, options)
.then(res => checkStatus(res))
.then(res => res.json())
.catch(res => this.handleResponseError(res));
}
post(url, options) {
return super.post(url, options)
.then(res => checkStatus(res))
.then(res => res.json())
.catch(res => this.handleResponseError(res));
}
put(url, options) {
return super.put(url, options)
.then(res => checkStatus(res))
.then(res => res.json())
.catch(res => this.handleResponseError(res));
}
del(url, options) {
return super.del(url, options)
.then(res => checkStatus(res))
.then(res => res.json())
.catch(res => this.handleResponseError(res));
}
updateHeader(headers) {
this.defaultHeaders = headers;
}
get errors() {
return this.errorEvents;
}
handleResponseError(res) {
this.errorEvents.next(res);
return Observable.throw(res);
}
}
export default CustomFFetch;
Reactの場合であれば、一番上のコンポーネントでエラーを購読して、あとはよしなにさばいてあげればよいでしょう。
componentDidMount() {
customFFetch.errors.subscribe((res) => {
if (res.status === 401) {
// do somthing
} else if (res.status === 404) {
// do somthing
}
});
}
SPAでない場合もエラーハンドリングの制御が散らばらず見通しがよさそうです。
共通でエラーハンドリングができると、都度同じような記述を書かなくてよくなるので楽ですね。
さいごに
今回はエラーだけを購読できるようにしましたが、そうするとGETやPOSTなどの各メソッドもPromiseではなくObservableを返すようにして、RxJSをいろいろ試してみたくなってきますね。
まだまだRxJSの知見が浅く、使いこなせてる感がしないので、もっと積極的に使っていこうと思いました。
LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。