fetchJsonWithTimeoutSignal

JavaScript
// Fetches JSON with a hard timeout and explicit HTTP/JSON error handling.
export async function fetchJson(url, { timeoutMs = 10_000, headers = {} } = {}) {
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
    throw new RangeError('timeoutMs must be a positive number');
  }
  let response;
  try {
    response = await fetch(url, {
      method: 'GET',
      headers: { Accept: 'application/json', ...headers },
      signal: AbortSignal.timeout(timeoutMs),
    });
  } catch (error) {
    if (error.name === 'TimeoutError') {
      throw new Error(`GET ${url} timed out after ${timeoutMs} ms`, { cause: error });
    }
    throw error;
  }
  if (!response.ok) {
    throw new Error(`GET ${url} failed with HTTP ${response.status}`);
  }
  const text = await response.text();
  try {
    return text === '' ? null : JSON.parse(text);
  } catch (error) {
    throw new SyntaxError(`GET ${url} returned invalid JSON`, { cause: error });
  }
}