-1

I have a closure to retrieve a token string:

var getAccessToken = { () -> (String) in
    var token = String("")
    credentialsManager.credentials(minTTL: 60) { result in
        switch result {
        case .success(let credentials):
            token = credentials.accessToken         
        case .failure(let error):
            token = ""
        }
    }
  return token
}

When I call getAccessToken() from another function like below, it always returns empty, as it doesn't wait for credentialsManager.credentials closure to return:

    func getUserProfile(completion: @escaping (UserProfile) -> ()) {
                                      
        let accessToken = getAccessToken()
        
        let url = URL(string: "https://asdf.com")!

        enum DataTaskError: Error {
          case invalidResponse, rateLimited, serverBusy
        }
        
        var request = URLRequest(url: url)
 
        request.addValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
 
        URLSession.shared.dataTask(with: request) { (data, _, _) in
         
            guard let data = data else { return }
            
            do {
                let userProfile = try JSONDecoder().decode(UserProfile.self, from: data)
                
                DispatchQueue.main.async {
                    completion(userProfile)
                }
                
            } catch {
                print(error.localizedDescription)
            }
           
        }
        .resume()
    }

I tried using a completion handler with getAccessToken:

func getAccessToken(completion: @escaping () -> (String)) { 
   //...
   completion(token)
}

but Swift errored: Argument passed to call that takes no arguments

alyx
  • 2,593
  • 6
  • 39
  • 64
  • You need to change our nested function accept a completion handler and return `Void`, for example `var getAccessToken = { (_ then: @escaping (String) -> ()) -> Void in ... }`, then when you want to return the token you would use `then(credentials.accessToken)` - the function CAN NOT return a value, as the result won't be known until some point in the future – MadProgrammer Jun 01 '22 at 02:22
  • @MadProgrammer thanks, though Swift erroring with `Missing argument for parameter #1 in call` when calling `let accessToken = getAccessToken()` – alyx Jun 01 '22 at 02:27
  • You now need to call it so that it will accept a compiletion block, it will NOT return you the value via the function call, but instead, will pass the value to you completion block, `getAccessToken { token in ... }` – MadProgrammer Jun 01 '22 at 02:30
  • `URLSession.shared.dataTask` is a bad ground task, it's executing in a different thread, thus the reason for the completion blocks – MadProgrammer Jun 01 '22 at 02:31

2 Answers2

2

You return the token before the credentialsManager.credentials generate the token in the first case. Second case your completion handler syntax is wrong for this scenario. Use the proper completion handler as below.

// completion will return the token, if credentialsManager is able created the token successfully
// in any case credentialsManager failed to generate the token it will return the underlying error

   func getAccessToken(completion: @escaping (Result<String,Error>) -> Void) {
        credentialsManager.credentials(minTTL: 60) { result in
            switch result {
            case .success(let credentials):
                completion(.success(credentials.accessToken))
            case .failure(let error):
                completion(.failure(error))
            }
        }
    }
    
    func getUserProfile(completion: @escaping (UserProfile) -> ()) {
        
        getAccessToken { result in
            switch result {
            case .success(let token):
                // What ever you do with token
                // here
                
                break
            case .failure(let error):
                // Handle error case
                break
            }
        }
        
    }
Sreekuttan
  • 1,579
  • 13
  • 19
  • 2
    While I see this as a potential good answer, please add some kind of explanation as to what is being done. It helps, otherwise an answer like this just encourages copy/paste without trying to understand what the code is doing. – Chucky Jun 01 '22 at 04:40
1

You are not using DataTaskError in your function. Given that, there is no reason to use a do/catch.

var userProfile: UserProfile {
  get async throws {
    let userProfile = try JSONDecoder().decode(
      UserProfile.self,
      from: await URLSession.shared.data(
        for: {
          var request = URLRequest(url: .init(string: "https://asdf.com")!)
          [ ("Bearer \(getAccessToken())", "Authorization"),
            ("application/json", "Content-Type")
          ].forEach {
            request.addValue($0, forHTTPHeaderField: $1)
          }
          return request
        } ()
      ).0
    )

    return await MainActor.run { userProfile }
  }
}