In March 2018, I wrote a piece titled Making illegal states unrepresentable, in which I argue that the “correct” result type for URLSession
completion handlers is Result<(Data, URLResponse), (Error, URLResponse?)>
.
In other words:
- If the network requests succeeds, you receive
(Data, URLResponse)
— the response body asData
and aURLResponse
representing the response metadata. - If the request fails, you receive
(Error, URLResponse?)
— an error and an optionalURLResponse
, because the request may have failed after receiving the initial response from the server.
Now, I don’t expect Apple to change the types of all completion handlers in their frameworks to use Result
in Swift (although it would be cool and they did this before).
URLSession.DataTaskPublisher
But the introduction of the Combine framework gave Apple a chance to revisit the result type of a URLSession
data task in a Swift-only context. Here’s what they chose for URLSession.DataTaskPublisher
:
- Successful requests publish a value of type
(data: Data, response: URLResponse)
, as expected. - Failed requests fail with a plain
URLError
. So you will never receive aURLResponse
value with the error.
By throwing away some error information that most apps would probably never use, Apple did the pragmatic thing. In fact, they couldn’t have used an (Error, URLResponse?)
tuple as a failure type because a publisher’s failure type must conform to the Error
protocol and tuples can’t do that (for the same reason, the result type I proposed wouldn’t work as-is with the Result
type in the standard library).
Alternatives
What they could have done is either define a new struct that wraps a URLError
and a URLResponse?
, or add a field to URLError
for the optional response:
struct URLError {
...
/// The response received from the server if we received
/// a complete response before the error occurred.
var response: URLResponse?
}
I like this second option because it’s also lightweight and pragmatic, at least from the user’s perspective. But I’m not sure how easy it would have been to implement given that URLError
is a really just a façade for an NSError
that’s bridged from Objective-C. I guess if the error handling code inside URLSession
added the response object to the NSError
’s userInfo
dictionary, a computed property would suffice on the URLError
side to extract it, but 🤷♂️.