0
\$\begingroup\$

I'm struggling with asynchronous code. Does anyone have suggestions of a better way to do the following?

What I'm doing: hitting an API (google's, actually), with a call that might get a 401 if the oauth access token has expired. If that happens, I call out to a different endpoint to refresh the access token, and then try the original call again.

Constraints: I'm trying to do this without any external libraries, which (alas) means no promises. Also, I'd prefer not to implement the delegation pattern, even though I know that's idiomatic swift, just because I dislike classes and the rest of the cumbersome baggage that delegation creates. But maybe I just need to get over that?

Thus, the best solution I can come up with is a monstrosity that involves a factory function that returns a function that rewrites the original request with a new token (passed as a get parameter for GoodReasons™) and calls it, this function itself passed as a callback to the function that goes and refreshes the token. That seems... like a terrible unintuitive and ugly way to do it. But I can't dream up anything simpler.

The code looks like this:

func postData(callback:@escaping (Data) -> Void){
// ... snip... the initial request
if resp.statusCode == 401 {
                    retryCall(request: request, callback: callback)
                    return
}

func retryCall(request: URLRequest, callback:@escaping (Data) -> Void){
    let newPostFunction = refresherCallbackFactory(request: request, callback: callback)
    refreshAccess(callback: newPostFunction)
}

func refresherCallbackFactory(request: URLRequest, callback:@escaping (Data) -> Void) -> (String) -> Void {
    func tokenTaker(_ tokenJSON: String) {
        let tjParsed = parseRefreshTokenJson(json: tokenJSON.data(using: .utf8)!)
        let token = tjParsed.accessToken
        var url = request.url!
        var newRequest = request
        var components = URLComponents(url: url, resolvingAgainstBaseURL: false)!
        components.queryItems = [
            URLQueryItem(name: "uploadType", value: "multipart"),
            URLQueryItem(name: "access_token", value: token)
        ]
        url = components.url!
        newRequest.url = url

        let session = URLSession.shared
        // ...snip... code generating a task based on the rebuilt request and handling errors, which calls the original callback on success per next line
            callback(data!)
        })
        task.resume()
        }
    return tokenTaker
}

func refreshAccess(callback:@escaping (String) -> Void){
    let endpoint = "https://www.googleapis.com/oauth2/v4/token"
    let queries = ["refresh_token": refreshToken.get()!,
                   "client_id": clientKey.get()!,
                   "grant_type": "refresh_token"]
    post(url: endpoint, queries: queries, callback: callback)  // post is a generic helper function for issuing post requests
}

This runs just fine, but it seems like so so so much of a code smell...

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.