Fetch API in JavaScript: esempi pratici

Fetch API in JavaScript: esempi pratici

La Fetch API è l’interfaccia moderna per effettuare richieste HTTP/HTTPS in JavaScript. Offre una sintassi basata su Promise, supporta flussi, abort con AbortController, caricamento file, CORS e molto altro. In questo articolo imparerai tutto ciò che ti serve per usarla in modo efficace nel browser.

Prima richiesta: GET di base

// Esegue una GET e converte la risposta in JSON
fetch('https://api.example.com/items')
  .then(response => {
    if (!response.ok) {
      // response.ok è true per status 200–299
      throw new Error(`HTTP ${response.status} ${response.statusText}`);
    }
    return response.json(); // oppure .text(), .blob(), .arrayBuffer(), .formData()
  })
  .then(data => {
    console.log('Dati:', data);
  })
  .catch(err => {
    // Qui catturi errori di rete o quelli lanciati sopra
    console.error('Errore nella fetch:', err);
  });

Uso con async/await

async function loadItems() {
  try {
    const res = await fetch('https://api.example.com/items');
    if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error('Errore:', err);
  }
}
loadItems();

Impostare header e query string

// Query string con URL e URLSearchParams
const url = new URL('https://api.example.com/search');
url.search = new URLSearchParams({ q: 'notebook', limit: 10 }).toString();

const headers = new Headers({
 'Accept': 'application/json'
});

const res = await fetch(url, { headers });
if (!res.ok) throw new Error('Richiesta fallita');
const results = await res.json(); 

Inviare JSON: POST, PUT, PATCH

async function createItem(payload) {
  const res = await fetch('https://api.example.com/items', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json', // indica JSON nel body
      'Accept': 'application/json'
    },
    body: JSON.stringify(payload)
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

createItem({ title: 'Nuovo oggetto', price: 19.9 })
.then(console.log)
.catch(console.error); 

Eliminare risorse: DELETE

const id = 42;
const res = await fetch(`https://api.example.com/items/${id}`, { method: 'DELETE' });
if (!res.ok) throw new Error(`HTTP ${res.status}`);

Gestione degli errori

  • Errori HTTP: response.ok è false per status non 2xx. Non generano automaticamente eccezioni: devi controllare e lanciare manualmente.
  • Errori di rete: disconnessioni, CORS bloccato, nome host non risolvibile; questi generano eccezioni catturabili nel catch.
  • Parsing: se la risposta non è JSON valido, response.json() rigetta.
try {
  const res = await fetch('/api');
  if (!res.ok) {
    const text = await res.text().catch(() => '');
    throw new Error(`HTTP ${res.status}: ${text || res.statusText}`);
  }
} catch (err) {
  // err.name può essere 'TypeError' per errori rete/CORS
  console.error(err);
}

Timeout e annullamento con AbortController

Fetch non ha timeout nativo; puoi abortire con AbortController.

function fetchWithTimeout(resource, options = {}) {
  const { timeout = 8000 } = options;
  const controller = new AbortController();
  const id = setTimeout(() => controller.abort('timeout'), timeout);

  return fetch(resource, { ...options, signal: controller.signal })
  .finally(() => clearTimeout(id));
}

try {
  const res = await fetchWithTimeout('/lento', { timeout: 5000 });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const data = await res.json();
  console.log(data);
} catch (err) {
  if (err.name === 'AbortError') {
  console.warn('Richiesta annullata per timeout');
} else {
  console.error(err);
}
} 

Ripetizioni con backoff esponenziale

async function fetchRetry(url, options = {}, retries = 3, backoff = 500) {
  try {
    const res = await fetch(url, options);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res;
  } catch (err) {
    if (retries > 0) {
      await new Promise(r => setTimeout(r, backoff));
      return fetchRetry(url, options, retries - 1, backoff * 2);
    }
    throw err;
  }
}

CORS in breve

  • CORS è controllato dal server con header come Access-Control-Allow-Origin.
  • Dal client puoi influire su mode ('cors', 'no-cors', 'same-origin') e su credentials.
  • Le richieste con credenziali richiedono header server appropriati (Access-Control-Allow-Credentials: true).
const res = await fetch('https://api.altrodominio.com/data', {
  mode: 'cors',
  credentials: 'include' // cookie/autenticazione inviate se consentite
});

Opzioni principali di fetch()

  • method, headers, body
  • mode: 'cors', 'no-cors', 'same-origin'
  • cache: 'default', 'no-store', 'reload', 'no-cache', 'force-cache', 'only-if-cached'
  • credentials: 'omit', 'same-origin', 'include'
  • redirect: 'follow', 'error', 'manual'
  • referrer, referrerPolicy, integrity, keepalive, signal

Request e Response

fetch() accetta un Request e ritorna un Response.

const req = new Request('/api/items', {
  method: 'GET',
  headers: new Headers({ 'Accept': 'application/json' }),
  cache: 'no-cache'
});

const res = await fetch(req);
console.log(res.status, res.headers.get('Content-Type'));

const clone = res.clone(); // consente letture multiple del body
const json = await res.json();
const text = await clone.text(); 

Scaricare file (Blob) e creare link

async function downloadCsv() {
  const res = await fetch('/report.csv');
  if (!res.ok) throw new Error('Download fallito');
  const blob = await res.blob();
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'report.csv';
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(url);
}

Caricare file con FormData

async function uploadFile(file) {
  const form = new FormData();
  form.append('avatar', file, file.name);
  form.append('userId', '123');

  const res = await fetch('/upload', {
    method: 'POST',
    body: form // niente Content-Type: lo imposta il browser con boundary
  });
  if (!res.ok) throw new Error('Upload fallito');
  return res.json();
} 

Stream di risposta: lettura progressiva

Nei browser che lo supportano, puoi leggere incrementi di dati con ReadableStream.

const res = await fetch('/stream');
if (!res.ok) throw new Error('HTTP ' + res.status);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let received = '';

while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  received += decoder.decode(value, { stream: true });
  // aggiorna UI parziale qui
}
received += decoder.decode(); // flush finale
console.log(received); 

Gestire avanzamento download

La Fetch API non espone eventi di progresso di alto livello, ma puoi derivarlo contando i byte letti dal reader (se il server invia Content-Length).

const res = await fetch('/grande-file.bin');
const contentLength = Number(res.headers.get('Content-Length')) || 0;
const reader = res.body.getReader();
let loaded = 0;
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  loaded += value.byteLength;
  if (contentLength) {
    console.log(`Progresso: ${Math.round(loaded / contentLength * 100)}%`);
  }
}

Streaming upload (avanzato)

Alcuni browser supportano request body come ReadableStream (e l’opzione sperimentale duplex: 'half' in alcuni contesti). Verifica la compatibilità prima di usarla in produzione.

// Esempio concettuale: carica chunk generati al volo
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue(new TextEncoder().encode('parte1'));
    controller.enqueue(new TextEncoder().encode('parte2'));
    controller.close();
  }
});

const res = await fetch('/upload-stream', {
  method: 'POST',
  body: stream,
  // duplex potrebbe essere richiesto da alcuni browser:
  // duplex: 'half'
}); 

Concorrenza: più richieste in parallelo

const ids = [1, 2, 3, 4, 5];
const promises = ids.map(id => fetch(`/items/${id}`).then(r => r.json()));
const results = await Promise.all(promises);
console.log(results);

Gestire errori singoli con Promise.allSettled

const endpoints = ['/a', '/b', '/c'];
const settled = await Promise.allSettled(
  endpoints.map(u => fetch(u).then(r => r.json()))
);
for (const s of settled) {
  if (s.status === 'fulfilled') console.log('OK:', s.value);
  else console.warn('Errore:', s.reason);
}

Paginatione lato API

async function fetchAllPages(baseUrl) {
  let next = baseUrl;
  const items = [];
  while (next) {
    const res = await fetch(next);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const page = await res.json();
    items.push(...page.data);
    next = page.next; // URL della prossima pagina secondo il contratto API
  }
  return items;
}

Caching lato client

  • Header HTTP: il server può controllare la cache con Cache-Control, ETag, Last-Modified.
  • Opzioni fetch: cache può forzare o bypassare la cache del browser.
// Bypassa cache
await fetch('/api/data', { cache: 'no-store' });

// Riutilizza da cache se possibile
await fetch('/api/data', { cache: 'force-cache' }); 

Gestire redirect e referrer

const res = await fetch('/go', { redirect: 'manual', referrerPolicy: 'no-referrer' });
if (res.type === 'opaqueredirect') {
  // In manual potresti gestire l'URL Location se disponibile (limitazioni di sicurezza si applicano)
}

Autenticazione: token Bearer

const token = 'eyJhbGciOi...'; // conserva in memoria o Secure Storage, evita localStorage se possibile
const res = await fetch('/api/secure', {
  headers: {
    'Authorization': `Bearer ${token}`,
    'Accept': 'application/json'
  }
});

Inviare e ricevere JSON in sicurezza

  • Imposta sempre Accept: application/json quando ti aspetti JSON.
  • Valida l’output del server prima di usarlo in UI.
  • Gestisci i casi di 204 No Content (niente body da leggere).
const res = await fetch('/api/maybe-empty');
if (res.status === 204) {
  console.log('Nessun contenuto');
} else {
  const data = await res.json();
  console.log(data);
}

Inviare form classici (x-www-form-urlencoded)

const params = new URLSearchParams();
params.set('username', 'alice');
params.set('password', 'segretissima');

const res = await fetch('/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: params
}); 

keepalive e richieste in unload

keepalive: true permette (entro un limite di dimensione) di inviare una richiesta quando la pagina sta per chiudersi, ad esempio per logging.

window.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    navigator.sendBeacon?.('/metrics', new Blob([JSON.stringify({ t: Date.now() })], { type: 'application/json' }));
    // In alternativa, con fetch e keepalive (dimensioni limitate):
    fetch('/metrics', {
      method: 'POST',
      body: JSON.stringify({ t: Date.now() }),
      headers: { 'Content-Type': 'application/json' },
      keepalive: true
    });
  }
});

Debug: ispezionare richieste e risposte

  • Usa gli Strumenti Sviluppatore del browser: pannello Rete per header, payload, tempi.
  • Logga sempre status, URL, timing e payload in fase di sviluppo.
async function loggedFetch(input, options) {
  const started = performance.now();
  try {
    const res = await fetch(input, options);
    console.info('FETCH', { url: res.url, status: res.status, ms: Math.round(performance.now() - started) });
    return res;
  } catch (err) {
    console.error('FETCH ERR', { input, err, ms: Math.round(performance.now() - started) });
    throw err;
  }
}

Pattern utili in produzione

  • Wrapper centralizzato per header comuni, token e gestione errori.
  • Abort per richieste obsolete (es. digitazione in una searchbox).
  • Cache applicativa (es. Map, IndexedDB) per evitare round trip ripetuti.
  • Schema validation (es. con Zod) per dati esterni.

Note su compatibilità e ambienti

  • Browser: Fetch è ampiamente supportata; verifica funzionalità avanzate (streaming upload) prima di usarle.
  • Service Worker: puoi intercettare e rispondere a fetch per caching offline.
  • Node.js: fetch è disponibile nativamente da Node 18+. Le API sono simili ma alcune opzioni differiscono.

Consigli

  • Controlla sempre response.ok e gestisci errori.
  • Usa async/await e try/catch per codice leggibile.
  • Imposta Accept e Content-Type adeguati.
  • Usa AbortController per timeout e ricerche dinamiche.
  • Conserva i token in modo sicuro e inviali con Authorization solo quando necessario.
  • Valida sempre il JSON di risposta prima di mostrarlo in UI.

Conclusione

Con la Fetch API puoi coprire la quasi totalità dei casi d’uso di rete nel browser: dalle semplici GET al controllo fine di streaming, cache, aborti e autenticazione. Usa i pattern mostrati per costruire un livello di accesso robusto e mantenibile.