Has fetch() caught up with XMLHttpRequest in JavaScript?
Published on

fetch()
is the newer alternative to
XMLHttpRequest
(or XHR in short)
with improved ergonomics, such as integration with promises / async
/ await
and stream
support.
It was introduced in 2015 as part of the ES6. Despite of that, many popular libraries still use XHR under the
hood for AJAX requests.
jQuery and Axios are some of them .
Where fetch() has caught up (or even surpassed XHR)
fetch()
has come a long way since 2015. In most regards fetch()
is very mature and can
do many things that
XMLHttpRequest
can do. Including:
-
Timeout. Although it's a bit awkward but still possible with the help of
AbortController
andsetTimeout
:const controller = new AbortController(); const id = setTimeout(() => controller.abort(), 2000); // abort after 2s console.log("Aborting after 2s"); const res = await fetch('/test?responseSize=90000', { signal: controller.signal }); console.log(await res.text()); clearTimeout(id);
-
Monitoring download progress. It can be done via
ReadableStream
:console.log("Starting request"); const res = await fetch('/test?responseSize=70000'); // we need a response of 70000 bytes const reader = res.body.getReader(); // reading the body as ReadableStream. // Note that it will not be possible to do res.text() // or something like that. const total = Number(res.headers.get('content-length')) ?? 0; let loaded = new Uint8Array(); do { const { done, value } = await reader.read(); if (done) { break; } const merged = new Uint8Array(value.length + loaded.length); merged.set(loaded); merged.set(value, loaded.length); loaded = merged; if (total) { console.log(Math.round((loaded.length / total) * 100) + '%') } } while (true); console.log(new TextDecoder().decode(loaded));
-
Streaming uploads.
fetch()
can send aReadableStream
request body, while XHR can only send a wholeBlob
/FormData
- no true streaming. Also, withReadableStream
it's kinda possible to estimate upload progress (count bytes enqueued, still not equivalent to the bytes successfully sent to the server and there is no easy way to send form data):let uploaded = 0 const buf = new Uint8Array(1024 * 50) const start = Date.now() var rs = new ReadableStream({ pull(ctrl) { uploaded += buf.byteLength; console.log('uploaded', uploaded); crypto.getRandomValues(buf); ctrl.enqueue(buf); if ((start + 1000) < Date.now()) { ctrl.close(); } } }) console.log("Starting request"); fetch('/test', { method: 'POST', body: rs, duplex: 'half', }).then(r => r.text()).then(console.log);
Where fetch() hasn't caught up
Despite of the last example, fetch()
still doesn't truly support upload progress. XHR, on the other
hand, supports both upload and download progress:
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = (e) => {
console.log(`${e.type}: ${e.loaded} bytes uploaded`);
}
xhr.onprogress = (e) => {
console.log(`${e.type}: ${e.loaded} bytes downloaded`);
}
xhr.onload = () => {
console.log("DONE", xhr.response);
};
const formData = new FormData();
formData.set("file", new Blob(["Some file content ".repeat(100000)], { type: 'text/plain' }));
console.log("Starting request");
xhr.open("POST", "/test?responseSize=50000");
xhr.send(formData);
Also, in addition to this, XHR supports synchronous requests. Although deprecated and discouraged to be used, it might have some use cases in specific situations.
These two reasons may explain why many popular libraries still lean more towards the old XHR.
Conclusion
fetch()
is now very mature and provides most of the (or even surpassing in some regards, such as
with streaming)
XMLHttpRequest
functionality. However it doesn't support true upload progress and synchronous
requests.
While both (especially the last one) aren't the most used features, library makers usually want to cover all the
use cases, which isn't surprising. So the old XMLHttpRequest
is a safer choice in this regard.