Molte dashboard interne — il pannello di controllo di un dispositivo, lo stato di una coda di lavoro, i sensori di un piccolo impianto — non hanno bisogno di un WebSocket: basta interrogare periodicamente il server con fetch e aggiornare la pagina. Il punto delicato non è la chiamata in sé, ma farla bene: mostrare un indicatore di caricamento senza sfarfallio, non continuare a interrogare il server quando la scheda del browser non è nemmeno visibile, e confermare le azioni distruttive senza usare il vecchio confirm() del browser.
Un helper fetch con indicatore di caricamento condiviso
Se più richieste possono essere in volo insieme (per esempio più widget che si aggiornano in parallelo), un semplice booleano "sto caricando" non basta: bisogna contare le richieste attive e nascondere l'indicatore solo quando l'ultima è finita.
let richiesteAttive = 0;
function mostraCaricamento() {
richiesteAttive++;
document.getElementById('barraCaricamento').classList.add('attiva');
}
function nascondiCaricamento() {
richiesteAttive = Math.max(0, richiesteAttive - 1);
if (richiesteAttive === 0) {
document.getElementById('barraCaricamento').classList.remove('attiva');
}
}
async function apiGet(percorso, { silenzioso = false } = {}) {
if (!silenzioso) mostraCaricamento();
try {
const risposta = await fetch(percorso);
const dati = await risposta.json();
if (!risposta.ok || dati.success === false) {
throw new Error(dati.error || `Errore HTTP ${risposta.status}`);
}
return dati;
} finally {
if (!silenzioso) nascondiCaricamento();
}
}L'opzione silenzioso è pensata proprio per il polling automatico in background: un aggiornamento che l'utente non ha richiesto esplicitamente non deve dare l'impressione che la pagina si sia bloccata.
Fermare il polling quando la scheda non è visibile
Un setInterval ingenuo continua a interrogare il server anche se la scheda è in un'altra finestra da ore. La Page Visibility API permette di saltare questi cicli inutili e di forzare un aggiornamento immediato non appena la scheda torna visibile:
async function aggiornaStato() {
if (document.hidden) return; // nessuno la sta guardando, si salta il giro
try {
const dati = await apiGet('/api/status', { silenzioso: true });
document.getElementById('badgeStato').textContent =
`attivo · uptime ${dati.uptime_secondi}s`;
} catch (errore) {
document.getElementById('badgeStato').textContent = `errore: ${errore.message}`;
}
}
aggiornaStato();
setInterval(aggiornaStato, 10000);
document.addEventListener('visibilitychange', () => {
if (!document.hidden) aggiornaStato();
});Confermare un'azione distruttiva con <dialog>
Per eliminazioni o azioni non reversibili, un <dialog> nativo si comporta meglio del vecchio confirm(): può essere stilizzato, gestisce da solo il tasto Escape e restituisce facilmente una Promise da attendere con await:
function confermaAzione(messaggio) {
const dialog = document.getElementById('dialogConferma');
dialog.querySelector('.messaggio').textContent = messaggio;
return new Promise((resolve) => {
const bottoneOk = dialog.querySelector('.btn-conferma');
const bottoneAnnulla = dialog.querySelector('.btn-annulla');
const chiudi = (risultato) => {
bottoneOk.removeEventListener('click', suOk);
bottoneAnnulla.removeEventListener('click', suAnnulla);
dialog.close();
resolve(risultato);
};
const suOk = () => chiudi(true);
const suAnnulla = () => chiudi(false);
bottoneOk.addEventListener('click', suOk);
bottoneAnnulla.addEventListener('click', suAnnulla);
dialog.addEventListener('cancel', suAnnulla, { once: true }); // tasto Escape
dialog.showModal();
});
}
// Utilizzo:
bottoneElimina.addEventListener('click', async () => {
if (await confermaAzione('Eliminare questo elemento?')) {
await apiDelete(`/api/elementi/${id}`);
}
});Quando conviene ancora il polling
Un WebSocket o un canale SSE restano la scelta migliore quando gli aggiornamenti sono frequenti (più volte al secondo) o quando serve una notifica immediata. Per stati che cambiano ogni pochi secondi, però, un polling ben scritto — silenzioso, sospeso quando la pagina non è visibile, con un indicatore di caricamento onesto — è più semplice da mantenere e non richiede un server con gestione delle connessioni persistenti.