XMLHttpRequest
Impara a usare XMLHttpRequest (XHR) in JavaScript: metodi open e send, readyState, eventi load/error/timeout, parsing JSON, POST e abort.
JavaScript è un linguaggio di programmazione fondamentale per lo sviluppo web, che consente esperienze utente dinamiche e interattive. Una delle caratteristiche chiave di JavaScript è la capacità di comunicare con i server, recuperare dati e aggiornare le pagine web in modo asincrono. Ciò viene realizzato principalmente tramite XMLHttpRequest (XHR). Questo articolo fornisce un'analisi approfondita di XMLHttpRequest, inclusi i suoi metodi, le proprietà e le applicazioni pratiche, con numerosi esempi di codice per facilitare l'apprendimento.
XMLHttpRequest funziona con le callback. Per il codice nuovo di solito si preferisce la Fetch API, che è basata sulle promise e funziona perfettamente con async/await. Vale comunque la pena capire XHR: lo si incontra nelle codebase più vecchie, ed è l'unica API nativa che riporta il progresso dell'upload con granularità fine.
Questa pagina illustra cos'è un oggetto XHR, come configurare e inviare una richiesta con open e send, come leggere la risposta tramite readyState e gli eventi load/error/timeout, come fare il parsing del JSON, come inviare dati con POST, come annullare una richiesta e come XHR si confronta con fetch.
Comprendere XMLHttpRequest
XMLHttpRequest (XHR) è un oggetto nativo del browser che consente a JavaScript di inviare una richiesta HTTP o HTTPS a un server e ricevere la risposta senza ricaricare la pagina. Il termine "XML" nel nome è storico: XHR può trasferire qualsiasi formato testuale o binario, e JSON è di gran lunga il formato più comune oggi. Questa capacità di comunicare con un server in background è il fondamento di quello che un tempo veniva chiamato AJAX (Asynchronous JavaScript and XML).
Il ciclo di vita di una richiesta è sempre lo stesso: creare l'oggetto, aprirlo (configurare metodo e URL), collegare i gestori di eventi per reagire al risultato, poi inviarlo.
Creare un oggetto XMLHttpRequest
Per prima cosa si crea un'istanza:
const xhr = new XMLHttpRequest();Una singola istanza di XMLHttpRequest gestisce una sola richiesta. Per effettuare una seconda richiesta, occorre creare un nuovo oggetto.
Effettuare una richiesta HTTP
Una volta che l'oggetto esiste, lo si configura con open, poi lo si invia con send.
Il metodo open
open inizializza una richiesta ma non la invia ancora. Accetta diversi parametri:
xhr.open(method, url, async, user, password);method: Il metodo HTTP da usare, ad es.'GET'o'POST'.url: L'URL a cui viene inviata la richiesta.async: Un boolean che indica se la richiesta è asincrona. Il valore predefinito ètruee si dovrebbe quasi sempre lasciarlo così (vedi l'avvertenza di seguito).user: Nome utente facoltativo per l'autenticazione HTTP.password: Password facoltativa per l'autenticazione HTTP.
Esempio:
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);Le richieste sincrone (xhr.open(method, url, false)) bloccano la pagina fino all'arrivo della risposta e sono deprecate sul thread principale. Mantenere sempre async impostato a true.
Il metodo send
send invia la richiesta al server. Tutti i gestori di eventi devono essere collegati prima di chiamarlo. Per una richiesta GET, lo si chiama senza argomenti. Per una POST, si passa il corpo della richiesta come argomento.
Esempio di richiesta GET:
xhr.send();Esempio di richiesta POST con dati codificati in formato form:
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('param1=value1¶m2=value2');Il metodo setRequestHeader aggiunge un header HTTP alla richiesta in uscita, e deve essere chiamato dopo open ma prima di send.
Gestire le risposte del server
Per gestire le risposte del server si possono usare vari listener di eventi.
L'evento onreadystatechange
L'evento onreadystatechange viene attivato ogni volta che la proprietà readyState cambia. La proprietà readyState contiene lo stato dell'XMLHttpRequest.
0: UNSENT1: OPENED2: HEADERS_RECEIVED3: LOADING4: DONE
Una richiesta è completata e riuscita solo quando readyState è 4 (DONE) e lo status HTTP rientra nell'intervallo di successo (tipicamente 200). Verificare solo readyState === 4 è un errore comune, perché il server potrebbe aver risposto con 404 o 500.
Esempio:
Sebbene onreadystatechange funzioni, il codice moderno preferisce in genere onload e onerror per una gestione delle richieste più semplice e leggibile. onreadystatechange viene utilizzato principalmente quando è necessario tracciare stati intermedi (come il progresso o la ricezione degli header).
L'evento load
L'evento load si attiva una volta che la risposta è arrivata completamente. È più semplice di onreadystatechange perché non occorre verificare readyState manualmente: si attiva solo nella fase DONE. Bisogna comunque controllare status per distinguere un vero successo da un errore HTTP.
Esempio:
L'evento progress
Per i download di grandi dimensioni è possibile monitorare il progresso con l'evento progress. Quando il server invia un header Content-Length, l'evento è determinato (lengthComputable è true) e si può calcolare una percentuale:
xhr.onprogress = function(event) {
if (event.lengthComputable) {
const percent = Math.round((event.loaded / event.total) * 100);
console.log(`Downloaded ${percent}%`);
}
};Per tracciare invece un upload, collegare i gestori a xhr.upload (xhr.upload.onprogress). Il progresso dell'upload è la funzionalità che Fetch non riesce ancora a replicare pienamente.
Gestire gli errori
Il codice robusto deve gestire i fallimenti. Due eventi coprono i casi di errore:
onerrorsi attiva in caso di errore a livello di rete: la richiesta non ha mai raggiunto il server, il DNS ha fallito, CORS l'ha bloccata, ecc. Si noti che un HTTP404o500non è un errore di rete: attivaload, nonerror, quindi è necessario ispezionare comunquestatus.ontimeoutsi attiva se la richiesta impiega più dixhr.timeoutmillisecondi. Un timeout di0(il valore predefinito) significa nessun limite.
Esempio:
Parsing delle risposte JSON
Le risposte del server sono molto spesso JSON. L'approccio più semplice è leggere il testo grezzo da xhr.responseText e analizzarlo con JSON.parse:
In alternativa, si può impostare xhr.responseType = 'json' prima dell'invio, e il browser effettuerà il parsing del corpo automaticamente. Il valore analizzato sarà disponibile su xhr.response (non xhr.responseText):
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);
xhr.responseType = 'json';
xhr.onload = function() {
if (xhr.status === 200) {
console.log('title: ' + xhr.response.title); // already an object
}
};
xhr.send();responseType accetta anche 'text', 'blob', 'arraybuffer' e 'document' per payload non JSON.
Inviare dati con POST
Per inviare un corpo JSON, impostare l'header Content-Type e serializzare l'oggetto con JSON.stringify:
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://jsonplaceholder.typicode.com/posts', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.status === 201) { // 201 Created
console.log('Created:', xhr.responseText);
}
};
xhr.send(JSON.stringify({ title: 'foo', body: 'bar', userId: 1 }));Per gli invii di form tradizionali, si usa invece un oggetto FormData: il browser imposta automaticamente il Content-Type multipart corretto, quindi non occorre chiamare setRequestHeader per questo.
Annullare una richiesta
Chiamare xhr.abort() per annullare una richiesta in corso, ad esempio quando l'utente naviga altrove o digita una nuova query di ricerca. Dopo l'annullamento, si attiva l'evento abort al posto di load:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts', true);
xhr.onabort = () => console.log('Request was cancelled');
xhr.send();
// Later, cancel it:
xhr.abort();L'equivalente con Fetch usa un AbortController.
XMLHttpRequest vs Fetch
| XMLHttpRequest | Fetch | |
|---|---|---|
| Modello di programmazione | Callback / eventi | Promise, funziona con await |
| Progresso upload | Sì (xhr.upload) | No |
| Progresso download | Sì (evento progress) | Tramite stream (più codice) |
| Annullamento | xhr.abort() | AbortController |
| Rifiuta su errore HTTP | No, si controlla status | No, si controlla response.ok |
Per la maggior parte del codice nuovo si preferisce Fetch. Si ricorre a XHR quando si ha bisogno di un reportage granulare del progresso dell'upload o si deve supportare un ambiente molto datato.
Conclusione
XMLHttpRequest consente a JavaScript di scambiare dati con un server in background: si crea l'oggetto, lo si apre con open, si collegano i gestori load/error/timeout e lo si invia con send. Ricordare di controllare sia readyState che status, di fare il parsing del JSON manualmente o tramite responseType, e di usare abort() per annullare le richieste obsolete. Per la maggior parte del codice nuovo la Fetch API basata sulle promise è la scelta migliore, ma capire XHR permette di lavorare agevolmente con le codebase più vecchie e nei casi — come il progresso dell'upload — in cui XHR risulta ancora superiore.