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…