0

I'm pretty sure this should be possible and I'm just searching for the wrong thing, but what I'm going to do is perform a 2 network requests in the same publisher chain.

Basically I'm trying to fetch a JSON Web token (from the web or locally) then perform the requested fetch using that JWT.

I've created a custom Subscription and Publisher for getting my JWT, and it stores the token locally and checks if it's valid so it can be reused, renewed or requested for the first time. However I'm having trouble working out how to then use this with the fetch.

This is where I am (please ignore the force unwrap for now):

JWTState.publisher(JWTState.urlRequest!)
    .tryMap { (jwtState) -> String in
        guard jwtState.isValid == true else {
            throw JWTError.invalidJWT
        }
        return jwtState.jwt
    }
    .map { (jwt) -> URLRequest in
        URLRequest.jsonRequest(url: url, jwt: jwt)
    }

At that point I have a valid URLRequst for the fetch using a valid JWT, but I just can't work out how I call the next URLSession.shared.dataTaskPublisher or URLSession.shared.dataTask in the chain.

I'm hoping I've missed some publisher or function, or someone can steer me in the right direction. I'm also guessing I might have to create another custom Subscription and Publisher pair, but at the moment I can't see how doing that would help.

Thanks in advance.

Baza207
  • 2,123
  • 1
  • 22
  • 40

1 Answers1

1

This is what flatMap is for. flatMap acts on each value, but instead of mapping it to another value, it maps it to a publisher, which in your case would be a second url request.

The general idea is:

let jwtRequest = URLRequest(...)

let fetchDataPublisher = URLSession.shared
   .dataTaskPublisher(for: jwtRequest)
   .map(\.data)
   .decode(type: Token.self, decoder: JSONDecoder()) // get the token
   .flatMap { token -> AnyPublisher<Data, Error> in
       let apiRequest = URLRequest(...) // with token
       
       return URLSession.shared
                 .dataTaskPublisher(for: apiRequest)
                 .map(\.data)
                 .mapError { $0 as Error }
                 .eraseToAnyPublisher()
   }
   .decode(type: Something.self, decoder: JSONDecoder())

This is a simplified example, since I didn't handle errors and took liberties with how you extract the token.

New Dev
  • 48,427
  • 12
  • 87
  • 129
  • I swear I tried this and I couldn’t get it to work, but maybe I was doing something else wrong as well. I tried so many things. Thanks though, I’ll give this a try later and see if it works. ‍♂️ – Baza207 Oct 16 '20 at 15:28
  • Right, I've tried that, but I'm just getting `Value of protocol type 'Publisher' cannot conform to 'Publisher'; only struct/enum/class types can conform to protocols` and `1. Required by instance method 'flatMap(maxPublishers:_:)' where 'P' = 'Publisher'` Only change I have from your above code is I have a `.decode()` step between `.map(\.data)` and `.flatMap`. – Baza207 Oct 16 '20 at 16:22
  • Oh, I forgot to add `.decode`... Yeah, `.flatMap` requires a bit of hand-holding for the compiler to properly infer types (though, your error implies something else). I updated my answer - see if this helps. – New Dev Oct 16 '20 at 16:58
  • Bingo, that did it. I knew it must be something simple like that. Thanks for the help. – Baza207 Oct 16 '20 at 17:43
  • 1
    @NewDev OK I _think_ I've found a closer parallel as a dup... I adapted that discussion in my online book, https://www.apeth.com/UnderstandingCombine/operators/operatorsTransformersBlockers/operatorsflatmap.html – matt Oct 16 '20 at 21:10
  • @matt - yeah, that looks like a better dup. Thanks! Great book, btw. I often find myself referring to it (and this one too https://heckj.github.io/swiftui-notes/) – New Dev Oct 16 '20 at 21:19