Skip to content

Instantly share code, notes, and snippets.

@stinger
Last active January 28, 2023 18:07
Show Gist options
  • Star 45 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save stinger/7cb1a81facf7f846e3d53f60be34dd1e to your computer and use it in GitHub Desktop.
Save stinger/7cb1a81facf7f846e3d53f60be34dd1e to your computer and use it in GitHub Desktop.
Combine - fetching data using URLSession publishers
import Foundation
import Combine
enum APIError: Error, LocalizedError {
case unknown, apiError(reason: String)
var errorDescription: String? {
switch self {
case .unknown:
return "Unknown error"
case .apiError(let reason):
return reason
}
}
}
func fetch(url: URL) -> AnyPublisher<Data, APIError> {
let request = URLRequest(url: url)
return URLSession.DataTaskPublisher(request: request, session: .shared)
.tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse, 200..<300 ~= httpResponse.statusCode else {
throw APIError.unknown
}
return data
}
.mapError { error in
if let error = error as? APIError {
return error
} else {
return APIError.apiError(reason: error.localizedDescription)
}
}
.eraseToAnyPublisher()
}
// Usage
guard let url = URL(string: "https://www.amazon.com") else { return }
fetch(url: url)
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
break
case .failure(let error):
print(error.localizedDescription)
}
}, receiveValue: { data in
guard let response = String(data: data, encoding: .utf8) else { return }
print(response)
})
@PaulSolt
Copy link

PaulSolt commented Sep 7, 2019

Thank you!

@pg8wood
Copy link

pg8wood commented Sep 11, 2019

Thanks, I've been looking for a good tryMap and mapError with URLSession.DataTaskPublisher example for awhile!

@DJBen
Copy link

DJBen commented Nov 27, 2019

Do you need to try fetch(url: url) at line 40?

@junweimah
Copy link

I tried this but I am getting finished with error [-999] Error Domain=NSURLErrorDomain Code=-999 "cancelled". It got cancelled immediately after starting. Anyone has an idea why? I use a .print("test") and i am getting

test: receive subscription: (DataTaskPublisher)
test: request unlimited
test: receive cancel

@workingDog
Copy link

same here, getting cancelled immediately after starting.

@junweimah
Copy link

junweimah commented Mar 22, 2020

@workingDog I dont know what is the cause but I found the solution for this

Dont call .sink like that, declare a subscriber and call it

like this:

let subscriber = AnyCancellable? 
subscriber.sink() 

@workingDog
Copy link

Thanks for the update.
It seemed an elegant way of fetching data. But after so much frigging around I
change tack and went with PromiseKit and the normal URLSession.
That works very well.

@alexanderwe
Copy link

alexanderwe commented Apr 16, 2020

@junweimah The reason is that sink is returning an Cancellable. If this cancellable is not referenced it gets deallocated immediately and is calling cancel. If you hold a reference to it this is not the case.

let sub = fetch(url: url)
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            break
        case .failure(let error):
            print(error.localizedDescription)
        }
    }, receiveValue: { data in
        guard let response = String(data: data, encoding: .utf8) else { return }
        print(response)
    })

Should work here

@MartinMajewski
Copy link

It does not work when you encapsulate the fetch inside a function and have the Cancellable let sub reside on the same scope because the completion is called long after the function is destroyed!
It is necessary to have a global Cancellable optional variable available that survives the entire scope of the caller of fetch!

@junweimah The reason is that sink is returning an Cancellable. If this cancellable is not referenced it gets deallocated immediately and is calling cancel. If you hold a reference to it this is not the case.

let sub = fetch(url: url)
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            break
        case .failure(let error):
            print(error.localizedDescription)
        }
    }, receiveValue: { data in
        guard let response = String(data: data, encoding: .utf8) else { return }
        print(response)
    })

Should work here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment