ご存知のように、fetch
はPromiseを返します。そして、JavaScriptには一般的にPromiseを「中断」する概念がありません。では、実行中の fetch
をキャンセルするにはどうすればよいでしょうか?例えば、サイト上のユーザーの操作が、fetch
がもはや必要ないことを示している場合などです。
このような目的のために、特別な組み込みオブジェクト AbortController
があります。これは fetch
だけでなく、他の非同期タスクも中断するために使用できます。
使い方は非常に簡単です
AbortController オブジェクト
コントローラーを作成する
let
controller =
new
AbortController
(
)
;
コントローラーは非常にシンプルなオブジェクトです。
abort()
というメソッドを1つ持ち、- イベントリスナーを設定できる
signal
というプロパティを1つ持ちます。
abort()
が呼び出されると
controller.signal
は"abort"
イベントを発行します。controller.signal.aborted
プロパティはtrue
になります。
一般的に、プロセスには2つの当事者がいます
- キャンセル可能な操作を実行する側、これは
controller.signal
にリスナーを設定します。 - キャンセルする側、これは必要に応じて
controller.abort()
を呼び出します。
完全な例を以下に示します(まだ fetch
は使用していません)
let
controller =
new
AbortController
(
)
;
let
signal =
controller.
signal;
// The party that performs a cancelable operation
// gets the "signal" object
// and sets the listener to trigger when controller.abort() is called
signal.
addEventListener
(
'abort'
,
(
)
=>
alert
(
"abort!"
)
)
;
// The other party, that cancels (at any point later):
controller.
abort
(
)
;
// abort!
// The event triggers and signal.aborted becomes true
alert
(
signal.
aborted)
;
// true
ご覧のとおり、AbortController
は abort()
が呼び出されたときに abort
イベントを渡すための手段にすぎません。
AbortController
オブジェクトを使用せずに、コード内で同じ種類のイベントリスニングを独自に実装することもできます。
しかし、重要なのは、fetch
が AbortController
オブジェクトの使用方法を知っていることです。それは fetch
に統合されています。
fetch での使用
fetch
をキャンセルできるようにするには、AbortController
の signal
プロパティを fetch
オプションとして渡します
let
controller =
new
AbortController
(
)
;
fetch
(
url,
{
signal:
controller.
signal
}
)
;
fetch
メソッドは AbortController
の使用方法を知っています。 signal
の abort
イベントをリッスンします。
अब, 中断するには, controller.abort()
を呼び出します
controller.
abort
(
)
;
これで完了です。fetch
は signal
からイベントを取得し、リクエストを中断します。
fetch が中断されると、その Promise は AbortError
で拒否されるため、例えば try..catch
で処理する必要があります。
1 秒後に中断される fetch
の完全な例を以下に示します
// abort in 1 second
let
controller =
new
AbortController
(
)
;
setTimeout
(
(
)
=>
controller.
abort
(
)
,
1000
)
;
try
{
let
response =
await
fetch
(
'/article/fetch-abort/demo/hang'
,
{
signal:
controller.
signal
}
)
;
}
catch
(
err)
{
if
(
err.
name ==
'AbortError'
)
{
// handle abort()
alert
(
"Aborted!"
)
;
}
else
{
throw
err;
}
}
AbortController はスケーラブルです
AbortController
はスケーラブルです。複数の fetch を一度にキャンセルできます。
多くの urls
を並列してフェッチし、単一のコントローラーを使用してすべてを中断するコードのスケッチを以下に示します
let
urls =
[
...
]
;
// a list of urls to fetch in parallel
let
controller =
new
AbortController
(
)
;
// an array of fetch promises
let
fetchJobs =
urls.
map
(
url
=>
fetch
(
url,
{
signal:
controller.
signal
}
)
)
;
let
results =
await
Promise.
all
(
fetchJobs)
;
// if controller.abort() is called from anywhere,
// it aborts all fetches
fetch
とは異なる独自の非同期タスクがある場合、単一の AbortController
を使用して、fetch とともにそれらを停止できます。
タスク内で abort
イベントをリッスンするだけで済みます
let
urls =
[
...
]
;
let
controller =
new
AbortController
(
)
;
let
ourJob =
new
Promise
(
(
resolve,
reject
)
=>
{
// our task
...
controller.
signal.
addEventListener
(
'abort'
,
reject)
;
}
)
;
let
fetchJobs =
urls.
map
(
url
=>
fetch
(
url,
{
// fetches
signal:
controller.
signal
}
)
)
;
// Wait for fetches and our task in parallel
let
results =
await
Promise.
all
(
[
...
fetchJobs,
ourJob]
)
;
// if controller.abort() is called from anywhere,
// it aborts all fetches and ourJob
まとめ
AbortController
は、abort()
メソッドが呼び出されると、そのsignal
プロパティでabort
イベントを生成するシンプルなオブジェクトです(また、signal.aborted
をtrue
に設定します)。fetch
はそれと統合されています。signal
プロパティをオプションとして渡し、fetch
はそれをリッスンするため、fetch
を中断できます。- コードで
AbortController
を使用できます。「abort()
を呼び出す」→「abort
イベントをリッスンする」という相互作用はシンプルで普遍的です。fetch
なしでも使用できます。