- more complicated API, request and response concepts are mixed together.
- lacks streaming, whole response is going to buffer in memory, not available for binary data
- more pleasant simpler API. Has Request and Response abstractions, can be used separately (for example in ServiceWorkers). Based on Promises.
- supports streaming, response.body is ReadableStream, you can read data chunk by chunk without buffering, available for binary data. Can access partial content while response is being received.
- has cache control support (default, no-store, reload, no-cache, force-cache, only-if-cached)
- has better control of cross-origin (same-origin, cors, no-cors) and credentialed requests (omit, same-origin, include)
- has control of redirects
- can set referrer policy (no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin)
- XHR can parse XML/HTML directly returning a Document in its "response" property. It's not possible to get a Document back directly from the Fetch API itself. But you can use DOMParser.parseFromString() and parse response.text() yourself.
- not supported in IE, there is a polyfill, but it can implement only a subset of features
- Previously it didn't have ability to abort requests. Now it's possible with a help of "AbortController+AbortSignal" API
- Does not support tracking progress. For downloads you can implement it yourself by getting total legnth from Content-Length response header and reading raw data chunk by chunk from ReadableStream (it's not trivial). And for tracking uploads progress, it's impossible right now.
- Does not support requests timeouts. Specification does not have request timeouts, so you need to implement your own timeout logic.
If you need features that Fetch API lacks, you can either use XHR directly or use some HTTP client library, like axios or superagent.