はじめに
AbortController
は非同期処理を中断するためのインターフェイスで、Node.js
では 15.0.0
から使えるようになりました。
今回は代表的な非同期処理である HTTP リクエストのキャンセルについて説明します。
HTTP でリクエストを送信するには、古くは XMLHttpRequest
を使っていましたが、昨今では Promise ベースの Fetch API
を使うことが多いと思います。
axios
や ky
といった HTTP クライアントライブラリの使用率は非常に高いですが、Universal API として 基本的な Fetch API
でのキャンセレーションについて説明します。
Fetch API について
モダンブラウザと Deno では Fetch API
を標準で利用可能です。Node.js
でも、node-fetch
があるので、Fetch API
は HTTP クライアントとしてユニバーサルに利用できるといって過言ありません。ですので、まずは Fetch API
での利用法をしっかり抑えましょう。
Fetch API
は第2引数に RequestInit
というオブジェクトを受け取ります。インターフェイスは次のとおりです。
signal
というキーは、 AbortSignal
を受け取ります。AbortSignal
は AbortController
クラスのメンバーです。
AbortController について
AbortController
は、非同期処理を中断できるシグナルオブジェクトを含むコントローラーです。コンストラクターからオブジェクトを生成できます。
AbortController
はシグナルオブジェクトの参照と abort
メソッドを持ちます。この signal
を fetch
に渡し、abort
メソッドを呼ぶことで、HTTP リクエストを中断できます。
例は Deno 以外の実行環境を想定しています。Deno は 1.10.3
の時点でまだ Fetch API
のキャンセレーションが実装されていません。main ブランチにマージされたのでおそらく近日中に利用できると思います。
Top-Level Await 記法を使用しています
上の例では、1000 ミリ秒後に、リクエストを中断します。 UI 上ではボタンのクリックイベントなどに abort
関数の呼び出しをバインドすることで、ユーザー主導のキャンセリングを実現できます。
これで中断はできましたが、次に中断後の処理について考えます。
中断をハンドルするにはいくつかの方法が存在します。それぞれ見ていきましょう。
Fetch API の reject
Fetch API
では、次の 2 つのケースで reject が発生すると定義されています。詳しくは仕様書を参照してください。
TypeError
AbortError
TypeError
はネットワークエラーの発生とともにスローされます。例えば、存在しない URL へのリクエストは TypeError
が発生します。
そして、もう一つのエラーが AbortError
です。これはリクエストの中断とともに発生します。
AbortError
を拾うことで、エラー処理をきっちり行うことができます。
また、TypeError
と AbortError
を拾い分けることで、ユーザーフレンドリーな通知などが行えます。
上の例では tryCatch
文でエラーキャッチをしましたが、もちろん Promise
の reject
関数からもエラーを拾うことができます。
イベントハンドラーとイベントリスナー
AbortSignal
のインターフェイスは次のとおりです。
AbortSignal
には onabort
というイベントハンドラがあります。
これに任意の関数をセットすることで、中断時にその関数が呼び出されます。
また、イベントリスナーの type
を abort
とすることで、同じように中断を監視できます。
また、読み取り専用プロパティの aborted
は AbortSignal
が中断されたかどうかを表します。
複数の HTTP リクエストを中断する
AboutController
は、複数の fetch
関数の呼び出しに渡すことができ、一括で HTTP リクエストを中断できます。
また、エラーのキャッチも一括で行えます。
複数回中断させる
AbortController
は一度 abort
を呼び出すと、
その AbortSignal
を参照にしている fetch
関数を再度実行できません。
例えば Vue では次のように書いてしまいがちになります。
この例では、AbortController
インスタンスは onClick
の度に再生成されるわけではないで、中断後 2 回目の HTTP リクエストを行えません。
インスタンスを fetch
の度に再設定する必要があるので、次のようにします。
変数のスコープ上、let
で宣言しなければならないのが残念ですが、これで fetch の度に新しいインスタンス設定できます。
Edit this page on GitHub