Reputation: 2483
I am working on a Combine request which I want to either execute or queue to execute after a certain event. Below is the scenario -
Below is my API where every request will be triggered -
public func fetchData<T: Codable>(to request: URLRequest) -> AnyPublisher<Result<T>, Error> {
if hasToken {
return self.urlSession.dataTaskPublisher(for: request)
.tryMap(self.parseJson)
.receive(on: RunLoop.main)
.subscribe(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
else {
// Store request somewhere
// Get token
// Execute stored request
}
}
I would really appreciate it if anyone can suggest how I can proceed with the else part of my code.
Upvotes: 2
Views: 997
Reputation: 49590
The approach you're starting with wouldn't work if there were multiple requests in quick succession happening. All these requests would see hasToken
as being false
, and they will all initiate a token request.
You could possibly create a var tokenRequested: Bool
property and synchronize access to it.
The way I approached this - not sure if this is the best approach - was by creating a pipeline through which all request would be queued:
class Service {
private let requestToken = PassthroughSubject<Void, Error>()
private let tokenSubject = CurrentValueSubject<Token?, Error>(nil)
var c: Set<AnyCancellable> = []
init() {
requestToken.zip(tokenSubject)
.flatMap { (_, token) -> AnyPublisher<Token, Error> in
if let token = token {
return Just(token).setFailureType(to: Error.self)
.eraseToAnyPublisher()
} else {
return self.fetchToken()
.eraseToAnyPublisher()
}
}
.map { $0 as Token? }
.subscribe(tokenSubject)
.store(in: &c)
}
private func fetchToken() -> AnyPublisher<Token, Error> {
// async code to fetch the token
}
}
Any request via requestToken
syncs with a value from tokenSubject
, so the first one goes together with the initial nil
, but subsequent ones wait until publishes the next value, which happens when the previous one completes.
Then, to make a request, you'd first get the token getToken()
, then make the request.
extension Service {
private func getToken() -> AnyPublisher<Token, Error> {
// request token, which starts queues it in the pipeline
requestToken.send(())
// wait until next token is available
return tokenSubject
.compactMap { $0 } // non-nil token
.first() // only one
.eraseToAnyPublisher()
}
func fetchData<T: Codable>(request: URLRequest) -> AnyPublisher<T, Error> {
getToken()
.flatMap { token in
// make request
}
.eraseToAnyPublisher()
}
}
Upvotes: 3