Signatur
Beschreibung
XMLHttpRequest (XHR) ist die klassische Browser-Schnittstelle für HTTP-Anfragen aus JavaScript und die technische Grundlage von „AJAX“. Trotz des Namens verarbeitet XHR beliebige Daten: Text, JSON, HTML-Dokumente, Blob und ArrayBuffer. Außerdem funktioniert es nicht nur mit HTTP(S).
Der Ablauf besteht aus festen Schritten:
- Instanz mit
new XMLHttpRequest()erzeugen. open(method, url)aufrufen.- Optional Header mit
setRequestHeader()setzen undresponseType,timeoutoderwithCredentialsfestlegen. - Die Anfrage mit
send(body)abschicken.
Das Ergebnis kommt über Ereignisse wie load, error, timeout, abort und progress oder über readystatechange zusammen mit readyState. Wichtige Eigenschaften:
readyState: Zustand von 0 bis 4.statusundstatusText: HTTP-Status der Antwort.response,responseTextundresponseXML: die empfangenen Daten.upload: Fortschrittsereignisse beim Hochladen.
In neuem Code nimmt man meist die Promise-basierte Fetch-API. XHR ist aber weiterhin nützlich, wenn man den Upload-Fortschritt anzeigen will (xhr.upload.onprogress). Das kann fetch() bis heute nicht einfach. XHR begegnet einem außerdem in älteren Codebasen und Bibliotheken.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $method Pflicht | string | Parameter von open(): HTTP-Methode, z. B. "GET", "POST", "PUT" oder "DELETE". |
|
| $url Pflicht | string | URL | Parameter von open(): Ziel-URL der Anfrage, absolut oder relativ zum Dokument. |
|
| $async | boolean | true | Parameter von open(): Bei false läuft die Anfrage synchron. Davon wird im Hauptthread abgeraten. |
| $user / password | string | null | null | Parameter von open(): Optionale Zugangsdaten für die HTTP-Authentifizierung. |
| $body | string | FormData | Blob | ArrayBuffer | URLSearchParams | Document | null | null | Parameter von send(): Inhalt der Anfrage. Bei GET und HEAD wird er ignoriert. |
Beispiele
JSON per GET laden
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
xhr.responseType = 'json';
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('Titel:', xhr.response.title);
} else {
console.error('HTTP-Fehler:', xhr.status);
}
});
xhr.addEventListener('error', () => console.error('Netzwerkfehler'));
xhr.send();
XHR als Promise mit async/await nutzen
const request = (method, url, body = null) =>
new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.responseType = 'json';
xhr.timeout = 5000; // 5 Sekunden
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = () =>
xhr.status < 400 ? resolve(xhr.response) : reject(new Error(`HTTP ${xhr.status}`));
xhr.onerror = () => reject(new Error('Netzwerkfehler'));
xhr.ontimeout = () => reject(new Error('Zeitüberschreitung'));
xhr.send(body ? JSON.stringify(body) : null);
});
const main = async () => {
const post = await request('POST', 'https://jsonplaceholder.typicode.com/posts', {
title: 'Hallo',
body: 'Welt',
userId: 1
});
console.log('Neue ID:', post.id);
};
main().catch(console.error);
Datei-Upload mit Fortschrittsanzeige
// HTML: <input type="file" id="datei"> <progress id="balken" max="100" value="0"></progress>
const input = document.querySelector('#datei');
const balken = document.querySelector('#balken');
input.addEventListener('change', () => {
const formData = new FormData();
formData.append('upload', input.files[0]);
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
balken.value = Math.round((e.loaded / e.total) * 100);
}
});
xhr.addEventListener('load', () => console.log('Upload fertig:', xhr.status));
xhr.open('POST', '/api/upload');
xhr.send(formData); // Content-Type (multipart) setzt der Browser selbst
});
Anfrage abbrechen mit abort()
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/grosse-datei');
xhr.onabort = () => console.log('Anfrage abgebrochen');
xhr.send();
// Nach 1 Sekunde abbrechen, z. B. wenn der Nutzer weiternavigiert
setTimeout(() => xhr.abort(), 1000);
// Wichtig · Fallstricke
- Das Ereignis
errormeldet keine HTTP-Fehler: Auch bei 404 oder 500 wirdloadausgelöst. Deshalb muss manstatusimmer selbst prüfen.errortritt nur bei Netzwerkfehlern oder CORS-Blockaden auf. In diesen Fällen iststatusgleich0. - Reihenfolge der Aufrufe:
setRequestHeader()funktioniert nur zwischenopen()undsend(). Andernfalls wird einInvalidStateErrorgeworfen.responseTypesetzt man vorsend(). - Synchrone Anfragen (
async = false) blockieren den Hauptthread und sind dort veraltet. Außerhalb von Workern darfresponseTypein diesem Fall nicht gesetzt werden. - CORS: Bei Anfragen an einen anderen Ursprung muss der Server passende
Access-Control-Allow-*-Header senden. Cookies werden nur mitwithCredentials = truemitgeschickt, und dann darf der Server nicht mitAccess-Control-Allow-Origin: *antworten. - Sicherheit: Serverantworten nie ungeprüft per
innerHTMLeinfügen, sonst droht XSS. BessertextContentverwenden oder die Inhalte bereinigen. Die Verbindungsziele lassen sich per CSP mitconnect-srceinschränken. - Verfügbarkeit: XHR gibt es in allen Browsern und in Web Workern, aber nicht in Service Workern und nicht nativ in Node.js. Dort verwendet man
fetch(). responseXMList nur bei XML- oder HTML-Antworten gefüllt. Bei HTML muss dafürresponseType = 'document'gesetzt sein.