
はじめに
はじめまして、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