Signatur
Beschreibung
Das progress-Ereignis gehört zum Interface XMLHttpRequestEventTarget, das sowohl von XMLHttpRequest selbst als auch von XMLHttpRequestUpload (erreichbar über xhr.upload) implementiert wird. Es wird periodisch ausgelöst – laut Spezifikation etwa alle 50 ms –, solange Daten übertragen werden. Auf xhr meldet es den Download-Fortschritt der Antwort, auf xhr.upload den Upload-Fortschritt des Request-Bodys.
Der Handler erhält ein ProgressEvent mit den Eigenschaften loaded (bisher übertragene Bytes), total (Gesamtgröße in Bytes) und lengthComputable (ob total bekannt ist). Damit lassen sich Fortschrittsbalken, Prozentanzeigen oder Restzeit-Schätzungen umsetzen – ein klassischer Anwendungsfall sind Datei-Uploads und große Downloads.
Registrieren lässt sich der Handler entweder per addEventListener("progress", handler) oder über die Eigenschaft onprogress. Die Listener müssen vor dem Aufruf von send() gesetzt werden – beim Upload sogar zwingend, da sonst in manchen Browsern gar keine Upload-Ereignisse erzeugt werden. Die Ereignisfolge lautet typischerweise loadstart → mehrere progress → load/error/abort/timeout → loadend.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $type Pflicht | string | Der Ereignisname "progress". |
|
| $handler Pflicht | (event: ProgressEvent) => void | Callback, der bei jedem Fortschrittsschritt ein ProgressEvent mit loaded, total und lengthComputable erhält. |
|
| $options | boolean|AddEventListenerOptions | false | Optionale Listener-Optionen wie once oder signal (zum Entfernen per AbortController). |
Rückgabewert
addEventListener gibt nichts zurück. Der Handler bekommt ein ProgressEvent-Objekt übergeben.Beispiele
Download-Fortschritt in Prozent anzeigen
const xhr = new XMLHttpRequest();
xhr.open('GET', '/dateien/grosses-video.mp4');
xhr.responseType = 'blob';
xhr.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const prozent = Math.round((event.loaded / event.total) * 100);
console.log(`Download: ${prozent} %`);
} else {
console.log(`Empfangen: ${event.loaded} Bytes (Gesamtgröße unbekannt)`);
}
});
xhr.addEventListener('load', () => console.log('Fertig, Status', xhr.status));
xhr.send();
Upload-Fortschritt mit <progress>-Element (onprogress)
const input = document.querySelector('#datei');
const balken = document.querySelector('progress');
input.addEventListener('change', () => {
const formData = new FormData();
formData.append('datei', input.files[0]);
const xhr = new XMLHttpRequest();
// Upload-Listener MUSS vor send() registriert werden
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
balken.max = event.total;
balken.value = event.loaded;
}
};
xhr.upload.onload = () => console.log('Upload abgeschlossen');
xhr.open('POST', '/api/upload');
xhr.send(formData);
});
Listener wieder entfernen und Übertragungsrate messen
const xhr = new XMLHttpRequest();
const start = performance.now();
const onProgress = (event) => {
const sekunden = (performance.now() - start) / 1000;
const kbps = (event.loaded / 1024 / sekunden).toFixed(1);
console.log(`${kbps} KiB/s`);
};
xhr.addEventListener('progress', onProgress);
xhr.addEventListener('loadend', () => {
// Aufräumen, sobald die Anfrage beendet ist
xhr.removeEventListener('progress', onProgress);
console.log('Listener entfernt');
});
xhr.open('GET', '/daten/export.csv');
xhr.send();
// Wichtig · Fallstricke
lengthComputableprüfen: Fehlt derContent-Length-Header (z. B. beiTransfer-Encoding: chunked), isttotalgleich0. Bei komprimierten Antworten (gzip/br) beziehen sichloadedundtotalje nach Browser auf die komprimierten Bytes – Prozentwerte können dann ungenau sein.- Upload-Listener vor
send(): Wirdxhr.upload.onprogresserst nachsend()gesetzt, werden keine Upload-Ereignisse gemeldet. Außerdem löst ein Upload-Listener bei Cross-Origin-Anfragen einen CORS-Preflight (OPTIONS) aus. - Kein garantiertes 100-%-Ereignis: Kleine Antworten können ganz ohne
progressauskommen. Den Abschluss immer überloadbzw.loadendbehandeln, nicht überloaded === total. - Performance: Das Ereignis kann sehr häufig feuern; aufwendige DOM-Updates ggf. per
requestAnimationFramedrosseln. - Kompatibilität: In allen modernen Browsern unterstützt. Die
fetch()-API bietet kein vergleichbares Ereignis – Download-Fortschritt dort nur überresponse.body.getReader(), Upload-Fortschritt praktisch nur mit XHR.