XMLHttpRequest
の代替としてPromise
を使って実装されたFetch APIですが、本当に使えるのでしょうか?
POSTでの通信やリダイレクトへの対応、中断やタイムアウトも必要です。フォームデータの送信や、検索パラメータの編集/解析も簡単な方が良いですね。
調べてみましょう。
これはFetch
に限ったことではありませんが、検索パラメータを文字列編集で作成するのはめんどくさいですね。日本語の場合、URLエンコードも必要になりますし。
URLSearchParams
というAPIが用意されています。
上記の関数は、次の文字列を返します。
URLSearchParams
には、検索パラメータの解析機能もあります。
上記の関数は、次の文字列を返します。
Fetch
は、デフォルトではGETメソッドにより送信しますが、当然他のメソッドを使うケースも多いですね。
デフォルトの状態から変更したい場合は、その内容をfetch
関数の第2引数にオプションとして指定します。
このサンプルで、第2引数に設定している内容に関しては、説明するほどのことも無いと思いますが、もちろん他にも多くのオプションがあります。
詳細は、「Fetch の使用 - Web API | MDN」をご覧ください。
呼び出し先で、POSTされたパラメータを一覧すると、下記の様になります(PHPによる例)。
Array ( [param1] => 1 [param2] => abcDEF [param3] => 日本語 )
デフォルトでは、内部でリダイレクトを行い、最終結果が返されます。
下記の例では、status_301.php
がステータスコード301
でstatus_200.php
にリダイレクトし、status_200.php
は"OK"
というテキストを返します。
「リダイレクトするURLと通信してみる」ボタンをクリックしてみてください。結果がその下に表示されます。尤も、実際にどう動いたかが分かるわけではありませんが(^_^;
結果表示 =>
リダイレクトを制御する場合、送信メソッドを変更する時と同様に、オプションを設定します。
下記例では、リダイレクトが指示された場合、エラーとします。
オプションのredirect
には、"follow"
"error"
"manual"
が指定できます。
"follow"
がデフォルトで、自動的にリダイレクトします。"error"
は、ネットワークエラーとなり、"manual"
は、アプリケーション側でリダイレクトします。
次のステータスコードを選択して、「リダイレクトするURLと通信してみる」ボタンをクリックしてみてください。結果がその下に表示されます。
200は成功、309は存在しないURLとなります。
結果表示 =>
FirefoxとChromiumでは、エラーとなるステータスコードが若干異なるようです。
マニュアルでリダイレクトさせる場合、以下のようになります。
と書くと、まるでリダイレクトが制御できるかのようですが、XSSの対策として特殊なタイプのResponse
が返ります。このResponse
は、ステータスコードやヘッダの内容などには一切アクセスできないので、リダイレクトが発生するステータスコードが返ったことが分かるだけです。
リダイレクトが発生するステータスコードが返ったかどうかの判定は、fetch
が返したレスポンスのタイプを見ます。
タイプが'opaqueredirect'
のレスポンスは、MDNに下記記述があるとおり、基本的には全ての情報にアクセスできません。
opaqueredirect: The fetch request was made with redirect: "manual". The Response's status is 0, headers are empty, body is null and trailer is empty.
「Response.type - Web API | MDN」より
次のステータスコードを選択して、「リダイレクトするURLと通信してみる」ボタンをクリックしてみてください。結果がその下に表示されます。
結果表示 =>
ステータスコード308によるリダイレクトは、仕様が比較的最近(と言っても2015年)に追加されたので、ネット上の解説サイトでも取り扱っていない場合がたまにあります。
レスポンステキストは「308 Permanent Redirect」なので、従来からある「301 Moved Permanently」と同じ様な意味合いですが、POSTでリクエストされた場合はPOSTでリダイレクトされなければならないところが、異なります(POSTに限った訳ではなく、リクエストと同じメソッドでリダイレクトしなければならないという意味です)。
結果表示 =>
POSTでリクエストしたときは、ちゃんとPOSTでリダイレクトされて、フォームデータもリダイレクト時に最初のレスポンス時と同じものが送信されるようです。まあ、当たり前ですね。
当初、Fetch APIにはタイムアウトや中断(アボート)の機能が存在しなかったので、XMLHttpRequestからダウングレードした等と言われていましたが、最近になってAbortControllerとAbortSignalというインターフェイスがAPIとして追加になりました。Fetch APIの一部という形ではなく、DOMの汎用的な機能として追加されたようです。
以下の例では、status_200_wait.php
は内部で5秒間スリープしてから'OK'という文字列を返します。
スリープしている間にsample8_stop
を呼び出すと、fetch
が例外を投げて中断します。
Fetchを中断させる準備としては、AbortController
のインスタンスを作成し、そこからAbortSignal
のインスタンスであるsignal
を取得して、fetch
にオプションとして渡します。
実際に中断させるためには、AbortController
のabort
メソッドを呼び出します。
結果表示 =>
AbortSignal
は、一旦中断(アボート)するとアボート状態に移行し元に戻すことができないので、Fetchを実行する度に作成する必要があります。
また、複数のfetch
の呼び出しに同じAbortSignal
を渡すことで、複数の通信を同時に中断させることもできます。
アボートのAPIが追加される以前にも、様々な方法で中断する実装がなされてきましたが、AbortSignal
を使った中断の利点はネットワークレベル(ソケットのレベル)で正しく切断されることでしょう。
abort
メソッドが呼ばれた時点でFINパケットがクライアントから飛ぶので、サーバ側で継続していた処理が終了してクライアントにレスポンスを送信すると、Connection reset by peer.
のエラーが発生します。
これを検知できれば、サーバ側で既にクライアントが接続を切っていることが分かるので、トランザクションをロールバックして処理が無かったことにもできます。尤も最近のWebアプリケーションで使われるサーバ側の実装では、通信エラーの検知が難しいので、使える場面は少ないかも知れませんが。
これは、タイマを使って中断させるだけなので、簡単です。
結果表示 =>