こんにちは、さるまりんです🐒
Web APIとやりとりするときに使うfetch
。とっても便利な関数ですが、単純に書くだけでは困ることも多いです。
通信が遅すぎて終わらなかったり、ネットワークが一時的に切れていたり、なぜか403 Forbidden
が返ってきてたり…。JavaScriptのエラー、わかりにくいことってありませんか?
こんなときにどう対処しているかをメモしておきます。
基本的なこと
fetchはこんなふうに書くと思います。
fetch('/api/data')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
APIを呼び出して結果受け取り、成功していたら〇〇、失敗だったら〇〇。
これで基本的には良いのですが、ちょっとした工夫があるとより安心です。
タイムアウト処理を追加
いつまで経っても返ってこない…。そんな時のためにタイムアウト処理を追加します。
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
try {
const res = await fetch(url, { signal: controller.signal });
clearTimeout(timer);
return await res.json();
} catch (e) {
if (e.name === 'AbortError') {
console.error('リクエストがタイムアウトしました');
} else {
console.error('通信エラー:', e);
}
throw e;
}
}
AbortController
を使うとfetch
を途中でキャンセルすることができます。↑ではtimeout
を5000に設定し、経過したらタイムアウトします。clearTimeout(timer);
は呼び出しが時間内に成功していたら止めないようにタイマーをクリアしています。
リトライ処理(自前で再試行)
タイムアウトで終わるのとともによく使うのが、失敗していたら再度トライ、リトライの仕組みですね。こんな感じで実装しています。
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP Error: ${res.status}`);
return await res.json();
} catch (e) {
if (i === retries - 1) throw e;
console.warn(`再試行中 (${i + 1}/${retries})...`);
await new Promise(r => setTimeout(r, 1000));
}
}
}
fetch
がエラーとなってしまった時は、for
ループを使って指定回数までは間隔をあけて再度アクセスしています。
並列 vs 順次処理
複数のAPIを呼び出すこともありますね。それを並列にするか順次にするかは場合によって変えますが、次のようにすることができるかと思います。
並列に複数fetch
const results = await Promise.all([
fetch('/api/one'),
fetch('/api/two')
]);
順番に1つずつfetch
for (const url of ['/api/one', '/api/two']) {
const res = await fetch(url);
const data = await res.json();
console.log(data);
}
特定のステータスコードで処理を分岐
特定のエラーだけは別の処理をしたいこともありますよね。↓のようにやってみました。
async function fetchWithStatusHandling(url) {
try {
const res = await fetch(url);
if (!res.ok) {
if (res.status === 403) {
console.warn(`403 Forbidden: ${url}`);
showFriendlyError('この操作にはアクセスできません');
return null;
} else if (res.status === 401) {
console.warn(`401 Unauthorized: ${url}`);
showFriendlyError('ログインが必要です');
return null;
} else {
throw new Error(`HTTP Error: ${res.status}`);
}
}
return await res.json();
} catch (e) {
console.error('fetch失敗:', e);
showFriendlyError('通信中にエラーが発生しました');
return null;
}
}
function showFriendlyError(msg) {
alert(msg); // alertでエラーメッセージ
}
fetch
でAPI呼び出しして、res.status
でステータスコードを取得。それが403 Forbidden
だったら?401 Unauthorized
だったら?そのほかの例外だったら?とメッセージの出し分けをしています。
showFriendlyError()
がユーザーに対してメッセージを提供する部分ですが、ここではalert()
呼び出ししてメッセージを表示しているだけですが、実際はもうちょっと凝ったUIとか入れたいですね。
fetch
はシンプルです。シンプルなだけにそのままでは困ることもあるので、タイムアウトやリトライ、エラー判定をできるようにすると、エラーに強くユーザーにも優しいものにできると思います。
使いやすいのが一番ですね!
読んでくださってありがとうございました。
それではまた!