jQueryのajaxはdone,failでなくthenにすべき
とある諸事情から、jqueryを利用したサーバレンダリングなシステムの試験・バグ対応を担当することになった。
本来ならばコーディングを担当したベンダーで試験・バグ対応も行う予定であったのだが、
とある騒動で出勤停止措置などの外的要因が発端となり、急遽システムの試験・バグ対応のヘルプ要員に任命された。
一からコードを読んで即座にバグ対応するノウハウが求められる中、jqueryのajax処理について気づきがあったので今回記事にしてみることに。
jQeuryのajaxの使い方(done/fail)
一般的にはjqXHR
のdone
やfail
でレスポンス処理をコーディングするだろう。そのシステムもこういう書き方だった。
$.ajax({
url: "/sample.json"
}).done((data, textStatus, jqXHR) => {
// 正常処理
}).fail((jqXHR, textStatus, errorThrown) => {
// 異常処理
});
だが、試験を進めていくと、done
処理内部に意図せず例外が発生するバグが含まれていることがわかった。
$.ajax({
url: "/sample.json"
}).done((data, textStatus, jqXHR) => {
// 中略
throw new Error("何らかの例外が発生する処理");
}).fail((jqXHR, textStatus, errorThrown) => {
// 異常処理
});
この場合、throwされた例外はcatchされず、処理されないため、どこかで処理してやらなければならない。
一番容易に思いつく対策は、done
の内部処理全部をtry-catch
することだろう。
$.ajax({
url: "/sample.json"
}).done((data, textStatus, jqXHR) => {
try{
// 中略
throw new Error("何らかの例外が発生する処理");
}
catch(e){
// 例外発生時の処理
}
}).fail((jqXHR, textStatus, errorThrown) => {
// 異常処理
});
だが、もしこのバグがすべてのajaxレスポンス処理で発生する可能性があるバグだとしたら・・・?
すべてのdone
にtry-catch
を記載するには修正箇所が多すぎる場合、どうするのがよいだろうか。
$.ajax
のオーバーライド
一番最初に思いついたのは、jqueryが提供している$.ajax
をオーバーライドして、独自の$.ajax
にしてしまう、という方法。
done
やfail
は$.Deferred()
を利用しているため、これを使えば「try-catchを共通化できるのでは?」と考えた。
// $.ajaxを退避
var orgAjax = $.ajax;
// $.ajaxをカスタマイズする
function customAjax(ajaxArgs) {
var settings = $.extend({}, $.ajaxSettings, ajaxArgs);
var deferred_org = $.Deferred();
var jqXHR_org = orgAjax(settings)
.done(function (data, textStatus, jqXHR) {
// 共通のdone処理
try {
// 個別のdone()を呼び出す(嘘)
deferred_org.resolveWith(this, [data, textStatus, jqXHR]);
}
catch (e) {
// 個別のdone内部で発生した例外の対処(嘘)
console.error(e);
}
}).fail(function (jqXHR, textStatus, errorThrown) {
// 個別のfail()を呼び出す(嘘)
deferred_org.rejectWith(this, [jqXHR, status, errorThrown]);
});
return $.extend({}, jqXHR_org, deferred_org);
}
// $.ajaxを上書き
$.ajax = customAjax;
だが実際は期待通りにならなかった。これではdoneでthrow
された例外をcatch
できないのだ。
Deferredを使った例外処理はthen().catch()
を使う
ここで登場するのが、done()
やfail()
ではなくthen()
である。
// $.ajaxを退避
var orgAjax = $.ajax;
// $.ajaxをカスタマイズする
function customAjax(ajaxArgs) {
var settings = $.extend({}, $.ajaxSettings, ajaxArgs);
var deferred_org = $.Deferred();
var jqXHR_org = orgAjax(settings)
.then(
function cmnDone(data, textStatus, jqXHR) {
// 個別のdone()を呼び出す
deferred_org.resolveWith(this, [data, textStatus, jqXHR])
},
function cmnFail(jqXHR, textStatus, errorThrown) {
// 個別のfail()を呼び出す
deferred_org.rejectWith(this, [jqXHR, textStatus, errorThrown]);
}
)
.catch((e) => {
// 個別のdoneで発生した例外をcatchできる
console.trace(e);
});
return $.extend({}, jqXHR_org, deferred_org);
}
// $.ajaxを上書き
$.ajax = customAjax;
こうすれば、今までの$.ajax
呼び出し箇所を変更することなく例外throwに対処できるようになった。
$.ajax({
url: "/sample.json",
})
.done(function (data, textStatus, jqXHR) {
// 例外をthrowしてもcustomAjaxで定義したcatchで処理される
throw new Error("test");
})
.fail(function (jqXHR, textStatus, errorThrown) {
})
まとめ
今回の経験から得るべきことは
-
$.ajax
(というか$.Deferred()
)のレスポンス処理はthen()
で書く- done内部に処理を書くときは、必ずdone内部に
try-catch
を書く
- done内部に処理を書くときは、必ずdone内部に
-
$.ajax
はオリジナルをそのまま使わない- 独自のajax関数でラップして使うべき