When several parts of an app ask for the same resource at the same time, Atom now collapses those identical GET requests into a single network call. The first caller starts the request; any caller that arrives while it is still in flight awaits that same result instead of issuing a second call. Every caller then receives the same response - or the same error, if it fails.
This builds on the concurrency work in v4.8.0, which introduced ServiceActor and coalesced token refresh so concurrent callers share a single refresh. This release extends the same idea to the requests themselves.
It is common for multiple parts of an app to request the same resource at once - several view models loading the same screen, a list and a detail view backed by the same endpoint, or a resource fetched during both warm-up and first render. Before this change, each of those callers issued its own network call, even though they were identical and in flight at the same moment. That wastes bandwidth, adds load on the service, and produces redundant work whose results are thrown away.
Proposed Solution
1. Coalesce Identical In-Flight GETs
ServiceActor gained deduplicatedResponse(for:), the single entry point for both the async/await and completion-based APIs. If a matching request is already in flight, the caller awaits it; otherwise it starts the request, records it, and clears the record once it finishes. The check-store step runs in one actor turn with no suspension between the lookup and the insert, so two concurrent callers can never both start a call.
2. Key on Pre-Auth Identity
Requests are matched on their HTTP method and fully-resolved URL - scheme, host, path, and query items. The key is built from the request before authorization, so the Authorization header applied later is deliberately not part of it. De-duplication therefore behaves the same across every authentication method (.none, .basic, .bearer).
3. Per-Request Opt-Out
Requestable gained allowsDeduplication, defaulting to true. Because it has a default implementation, existing conformers pick up the behavior without any code change. An endpoint opts out by returning false - for a GET that records a side effect on the server, or one whose response can change between two otherwise-identical concurrent calls.
Scope
De-duplication is coalescing, not caching. The in-flight record is cleared the moment the originating call finishes, so once that window closes the next call goes to the network as usual. Non-GET methods always execute on their own, since combining side-effecting requests would not be safe.
Changes:
- Added
deduplicatedResponse(for:)toServiceActorto coalesce identical in-flightGETrequests into a single network call. - Added
allowsDeduplicationtoRequestable(defaults totrue) as a per-request opt-out. - Keyed de-duplication on pre-auth identity (method + fully-resolved URL), so behavior is uniform across
.none,.basic, and.bearer. - Preserved the existing token-refresh coordination - concurrent bearer callers with an expired token still trigger exactly one refresh, and now exactly one
GET. - Added a
Request de-duplicationsection to the README and Usage docs.
Source Compatibility:
- This change is additive and does not impact existing source code.
- This change breaks existing source code.