はじめに
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 が発生すると定義されています。詳しくは仕様書を参照してください。
TypeErrorAbortError
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
