はじめに
はじめまして、株式会社iimonに新卒入社した、フロントエンジニアのかとうです🐣
時間のかかるfetchリクエストを途中でやめたくなったので、fetchを中止する方法をまとめました。
fetchとは
fetchは、Javascriptでリクエストを行うためのAPIです。非同期にサーバーから文書や写真などのデータを取得するために使用されます。
fetch関数は以下のように、引数を指定してリクエスを送信します。
fetch(resource, options)
- resource: 取得したいリソースのURLの文字列
- options: リクエストの設定を定義するオブジェクト(省略可)
optionsを設定するコード例
fetch('<https://example.com/api/data>', { method: 'POST', headers: { 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error));
fetchを中止する方法
未完了のfetchは中止することができます✊
どんなときにfetchを中止する?
リクエストを中止する場面として、以下の2つの場合が考えられます。
- ユーザーの操作による中止:ボタンがクリックされた場合など
- タイムアウトによる中止:一定時間が経過した場合
fetchを中止するためには、AbortController
とAbortSignal
を使用します。Abortは、「処理を中止する」という意味です。
AbortController
AbortController
はウェブリクエストを中止するためのAPIです。AbortController
のインスタンスを生成し、そのsignal
プロパティをfetchのオプションオブジェクトに設定することで、リクエストとAbortController
が紐づけられます。
const controller = new AbortController(); // インスタンスの生成 const signal = controller.signal; // signalプロパティ const data = fetch("https://example.com/data", { signal }) // リクエストとコントローラーの紐づけ .then((response) => response.json())
AbortSignal
AbortSignal
は中止の信号を送る役割を持っています。この信号はAbortController
から生成されます。リクエストを行う際にAbortSignal
をオプションとして渡すことで、この「信号」がリクエストと紐づけられます。その後、AbortController
のabort()
メソッドが呼び出されると、紐づけられたリクエストが中止されます。
fetchを中止する流れ
使用するデータ:JSONPlaceholderにある5000枚の画像情報
挙動:「Download」ボタンをクリックすると、JSONPlaceholderから画像情報を取得し、ランダムに選択された1つの画像情報を表示します。
ユーザーによる停止
AbortController()
でコントローラーを生成しますAbortController.signal
プロパティを使用して、関連するAbortSignal
を取得します- fetchの処理が始まると、
AbortSignal
をリクエストのオプションオブジェクトとして渡します。これにより、リクエストとコントローラーが紐づきます AbortController.abort()
を呼び出すことで、fetchの処理が中止されます- fetchは
AbortError
により失敗します
コード例
リクエスト中に「Stop」ボタンをクリックすると、リクエストを中止します
HTMLファイル (
abort.html
)<!DOCTYPE html> <html> <head> <title>Download</title> </head> <body> <button id="download">Download</button> <button id="stop">Stop</button> <div id="status"></div> <div id="results"></div> <script src="abortFetch.js"></script> </body> </html>
JavaScriptファイル (
abortFetch.js
)const startButton = document.getElementById('download'); const stopButton = document.getElementById('stop'); const statusDiv = document.getElementById('status'); const resultsDiv = document.getElementById('results'); let abortController = null; startButton.addEventListener('click', async () => { abortController = new AbortController(); // AbortControlleのインスタンスの生成 statusDiv.textContent = 'Downloading...'; try { const response = await fetch( 'https://jsonplaceholder.typicode.com/photos', { signal: abortController.signal } // AbortSignalを取得、設定 ); const responseData = await response.json(); statusDiv.textContent = 'Complete!'; const randomNumber = Math.floor(Math.random() * responseData.length); const data = responseData[randomNumber]; resultsDiv.innerHTML = ` <div> <p><strong>ID: ${data.id}</strong></p> <p><strong>TITLE: ${data.title}</strong></p> <img src="${data.thumbnailUrl}" alt="${data.title}"> </div> `; } catch (error) { if (error.name === 'AbortError') { statusDiv.textContent = 'Download Stopped'; } else { statusDiv.textContent = 'Error: ' + error.message; } } }); stopButton.addEventListener('click', () => { if (abortController) abortController.abort(); // リクエストを中止する });
クリックイベントの外部でAbortController
を生成すると、一度fetchが中止された後、次のクリック時には中止されたAbortController
が使用されます。その結果、次のfetchも中止された状態が続きます。
AbortSignal.aborted
AbortSignal.aborted
は、リクエストが中止されているかどうかを判定するプロパティです。
- fetchを停止する前の状態:
AbortSignal.aborted = false
- fetchを停止した後の状態:
AbortSignal.aborted = true
⚠️読み取り専用のため、AbortSignal.aborted
を書き換えることはできません
⇒クリックイベント内でAbortController
を生成することで、各リクエストごとに新しいコントローラーが使用され、中止された状態がリセットされます。
タイムアウトによるfetchの中止
AbortSgnal.timeout()
AbortSignal.timeout()
メソッドを使用することがで、時間制限を設けてfetchを中止することができます。fetchリクエスト中にAbortSignal.timeout()
が呼び出されると、fetchはTimeoutError
で失敗する結果になります。
AbortController.timeout()
をfetchオプションに設定することで、指定した時間が経過するとfetchの処理が自動的に中止されます- fetchは
TimeoutError
により失敗します
コード例
指定時間内にレスポンスが返ってこない場合、リクエストを中止します
HTMLファイル (
stop.html
)<!DOCTYPE html> <html> <head> <title>Download</title> </head> <body> <button id="download">Download</button> <div id="status"></div> <div id="results"></div> <script src="stopFetch.js"></script> </body> </html>
JavaScriptファイル(
stopFetch.js
)const startButton = document.getElementById('download'); const statusDiv = document.getElementById('status'); const resultsDiv = document.getElementById('results'); const requestFunction = async () => { statusDiv.textContent = 'Downloading...'; try { const response = await fetch("https://jsonplaceholder.typicode.com/photos", { signal: AbortSignal.timeout(10), }); const responseData = await response.json(); const randomNumber = Math.floor(Math.random() * responseData.length); const randomData = responseData[randomNumber]; resultsDiv.innerHTML = ` <div> <p><strong>ID: ${randomData.id}</strong></p> <p><strong>TITLE: ${randomData.title}</strong></p> <img src="${randomData.thumbnailUrl}" alt="${randomData.title}"> </div> `; statusDiv.innerText = "Complete!"; } catch (error) { if (error.name === "TimeoutError") { statusDiv.innerText = "Timeout Error"; } else { statusDiv.textContent = 'Error: ' + error.message; } } } startButton.addEventListener('click', requestFunction);
さいごに
AbortController
やAbortSignal
を使用することで、時間のかかるリクエストや不要になったリクエストを途中で停止することができます🐥
最後までご覧いただき、ありがとうございます!
弊社ではエンジニアを募集しております。興味を持って下さった方がいらっしゃれば、是非カジュアル面談でお話ししましょう! Wantedly / Green
参考
fetch() グローバル関数 - Web API | MDN