Home > OS >  Function doesn't wait for nested function before returning
Function doesn't wait for nested function before returning

Time:06-01

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

CodePudding user response:

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
            }
        }
        
    }

CodePudding user response:

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 }
  }
}
  • Related