Migrate to queueMicrotask to improve promise resolution for XHR events
Description
The current implementation resolves successful XHR responses using setTimeout(..., 0) inside xhr.onload:
https://github.com/JakeChampion/fetch/blob/ba5cf1ed2e02ebb96fa1e60b4fd2eb04071b60e4/fetch.js#L554-L557
This introduces a dependency on the host environment's macrotask scheduling mechanism even though the network request has already completed and xhr.onload has already fired.
In React Native (iOS), we observed cases where:
- The network request completed successfully.
xhr.onloadexecuted successfully.- The scheduled timeout was significantly delayed until a later user interaction or navigation event.
- The fetch promise remained unresolved until that timeout executed.
Because the promise resolution depends on a timer, any environment-specific issue affecting timer delivery can prevent completed requests from resolving promptly.
Proposed Change
Consider replacing the timeout-based scheduling with a microtask:
queueMicrotask(() => {
resolve(new Response(body, options));
});or an equivalent Promise-based microtask.
This would still preserve asynchronous fetch resolution semantics while avoiding reliance on timer scheduling for a response that has already completed.
Why This Helps
Microtasks are generally executed as part of the current JavaScript turn after the current call stack unwinds and do not depend on timer infrastructure. Also, I don't see any benefit of using setTimeout here because we are passing 0 as a timeout that is supposed to be executed as soon as possible. So, we should better use queueMicrotask here.
In our testing, replacing the timeout with a microtask resolved delayed-promise issues in React Native iOS while preserving expected fetch behavior.
Additional Context
The timeout appears to have been introduced to guarantee asynchronous resolution ref. The proposed change preserves that behavior while using a scheduling mechanism that is more closely aligned with modern JavaScript runtimes.
Source: JakeChampion/fetch