jQuery の ajax メソッドは、 error というコールバックをとることができる。 しかしこの error には、リクエストの内容が渡されない。 すると例えば、コールバックの中でリクエスト先の URL が取得したい!というときに困ることもあるというか困ったので解決方法を書き残す。

概要

  • settings のプロパティ beforeSend で Ajax 通信をフックする
  • フック内で jqXHR オブジェクトに URL などを格納する
  • コールバック内で jqXHR から URL などを参照する
  • $.ajaxSetup() で beforeSend にデフォルト値を設定しておけば、$.ajax() してるところには手を入れなくても大丈夫になる

環境

  • jQuery >= 1.5

手順

beforeSend でフックする

jQuery.ajax() | jQuery API Documentation にあるとおり、 $.ajax メソッドに渡す settings に beforeSend を設定すると、リクエストを送信する前に jqXHR に手を入れることができる。

beforeSend Type: Function( jqXHR jqXHR, PlainObject settings ) A pre-request callback function that can be used to modify the jqXHR (in jQuery 1.4.x, XMLHTTPRequest) object before it is sent. Use this to set custom headers, etc. The jqXHR and settings objects are passed as arguments. This is an Ajax Event. Returning false in the beforeSend function will cancel the request. As of jQuery 1.5, the beforeSend option will be called regardless of the type of request.

とあるように、ヘッダをいじりたい場合などを想定しているっぽい。

function の引数は jqXHR と settings が渡されるので、settings に設定されているものの中から必要なものを jqXHR に格納できる。

$.ajax({
  url: "exodus.html",
  data: {
    bansaku: "tsukita---!!!"
  },
  beforeSend: function(jqXHR, settings) {
    jqXHR.requestURL = settings.url;
    jqXHR.requestData = settings.data;
  },
  error: function(jqXHR, textStatus, errorThrown) {
    console.error("failed!!", "url:" + jqXHR.requestURL, "data:" + jqXHR.requestData);
  }
});

という感じで URL や data を取得できる。

Tips 1: settings に渡したものと、settings から取得できるものは違う(かもしれない)

beforeSend はリクエストの送信直前にフックするので、settings に渡したものは jQuery によって書き換えられている可能性がある。

例えば、上の例だと以下の様な感じになる。

key settings.key に渡したもの settings.kew から取得できるもの
url “exodus.html” “exodus.html?bansaku=tsukita—!!!”
data { bansaku: “tsukita—!!!” } undefined

なにがどう変換されるかは、jQuery.ajax() | jQuery API Documentation を読む。

Tips 2: $.ajaxSetup で一括フックする

$.ajaxSetup() で settings にデフォルト値を設定できる。 全部の $.ajax() に beforeSend を設定するのを DRY にしたいときとか、使ってるライブラリが内部で $.ajax() を使ってるけどそこには手を入れたくないときとかに使える。

$.ajaxSetup({
  beforeSend: function(jqXHR, settings) {
    jqXHR.requestURL = settings.url;
    jqXHR.requestData = settings.data;
  }
});

しかしもちろん、 $.ajax([settings]) の settings で上書きされるので注意する。

Tips 3: ajax 系の他の書き方でも使える?

1. $.ajax(url, [settings]) の場合

$.ajax("exodus.html", {
  data: {
    bansaku: "tsukita---!!!"
  },
  error: function(jqXHR, textStatus, errorThrown) {
    console.error("failed!!", "url:" + jqXHR.requestURL, "data:" + jqXHR.requestData);
  }
});
> failed!! url:exodus.html?bansaku=tsukita---!!! data:undefined

使える。

2. $.ajax().fail(function( jqXHR, textStatus, errorThrown ) {}) の場合

$.ajax({
  url: "exodus.html",
  data: {
    bansaku: "tsukita---!!!"
  }
}).fail(function( jqXHR, textStatus, errorThrown ) {
  console.error("failed!!", "url:" + jqXHR.requestURL, "data:" + jqXHR.requestData);
})
> failed!! url:exodus.html?bansaku=tsukita---!!! data:undefined

使える。

まとめ・感想

jQuery の ajax メソッドについて、error コールバック内でリクエストの情報(URL、data)が取得できない問題を、beforeSend フックを使うことで解決できた。

でも「解決」と言って良いほど良い方法かは微妙ではある。ちょっと考えただけでも、次のような問題がある。

  • settings.url は、https:// から入ってるかもしれないし、相対パスしか入ってないかもしれない
    • そしてそれは $.ajax() の使い方次第になる
  • settings.url や settings.data は、jQuery によって書き換えられる
    • GET か POST かでも変わるし、processDatatraditional の設定によっても変わる
    • そしてそれは $.ajax() の使い方次第になる

今回は、たまたま使っていた音声認識ライブラリが、API の内部で ajax を使って音声認識サーバーにリクエストを投げていて、 その ajax 部分はクライアントから完全に隠蔽されており、 かつクライアントから渡せるコールバックには jqXHR をそのまま投げつけるという万策尽きかける仕様だったので、 こんなワークアラウンドで乗り切った。