JSONPの404は捕捉できない


クロスオリジンでAjax通信をする場合に使うJSONP。
アクセス先のステータスコードが400系や500系などの場合、エラーとして捕捉できず無限に待ち続ける状態になる。

// jQuery使用
$.ajax({
  url: '//example.com/api/',
  type: 'GET',
  dataType: 'jsonp',
})
.done(function(){
  // 当然ここは実行されない
})
.fail(function(){
  // ここも実行されない!
});

なぜか

JSONPの原理を考えてみれば、当然といえば当然である。

  • コールバック関数を定義
  • scriptタグにアクセスポイントのURLを埋め込む
  • アクセスポイントのリソースは、JavaScriptファイルとして読込・実行され、用意しておいたコールバック関数が実行される

ここで、scriptタグで読み込む先が404とか500であった場合、読み込むべきリソースが無いのであるから、
当然ながら用意されたコールバック関数は実行されない。
response bodyに中身が入っていても、クライアント(ブラウザ)によってそれは無視される。

XHRと違って、JavaScriptはアクセスポイントへのリクエストを取り扱っているわけではない。
JSONPのアクセスでは、JavaScriptは、scriptタグを生成してコールバック関数の実行を指を咥えて待っているだけなのだ。後のリクエストはブラウザが勝手にやっているに過ぎない。

jQueryのドキュメントにも、エラーハンドリングできない旨は書いてある。
https://api.jquery.com/jquery.ajax/
settingsのerrorプロパティ

Note: This handler is not called for cross-domain script and cross-domain JSONP requests.

どうするのか

iframeを使ってアクセスポイントの中身を探る方法もあるようだが、
サーバがそれなりに素早く正常値を返してくれるのが期待できるのであれば、
timeoutを設定することで良いのではないか。

// jQuery使用
$.ajax({
  url: '//example.com/api/',
  type: 'GET',
  dataType: 'jsonp',
  timeout: 2000,
})
.done(function(){
  // 当然ここは実行されない
})
.fail(function(){
  // アクセスできない場合は、2秒後にこれが実行される
  // 正常にレスポンスが返ってきても場合でも、2秒以上かかってしまうと実行されてしまうのだが
});

JSONP自体は、本質的にJavaScriptに備わっているわけではない裏技的仕組みなので、エラー時にどうするかの真っ当な仕組みは用意されていないということだ。

timeoutでお茶を濁すような方法を紹介したが、
この辺の処理を厳密にやるべき案件なのであれば、JSONPなど使用せず、
同一オリジンでAPI提供するようにするか、Access-Control-Allow-Originヘッダーを付けるなど、インフラ側・サーバ側にちゃんと対応してもらおう。
何でもかんでもフロントエンドに吸収させようとしないでくれ。