swift-server/async-http-client

`whenComplete` does not work without wait()

Closed this issue · 2 comments

Hi, I'm new with swift-nio and want to make a simple swift cli tool that sends an API request. I tried following the readme to do something like this in my swift code

httpClient.execute(request: request).whenComplete { result in
            dump(result)
            switch result {
            case .failure(let error):
                print("Error \(error)")
            case .success(let response):
                print("Response: \(response)")
                if response.status == .ok {
                    print("Response: \(response)")
                } else {
                    print("Response is not ok")
                }
            }
        }

the output error is

▿ Swift.Result<AsyncHTTPClient.HTTPClient.Response, Swift.Error>.failure
  ▿ failure: HTTPClientError.cancelled
    - code: AsyncHTTPClient.HTTPClientError.(unknown context at $10587c078).Code.cancelled

One google result leads me to this same problem https://stackoverflow.com/questions/59434813/how-to-use-async-http-client
and the answer is, rather than using whenComplete , i should do it like this

let future = httpClient.execute(request: request)
        let response = try future.wait()
        print(response)

How can I make the whenComplete works?

When I just add a line try future.wait() ... it works but i need to discard its response too?

Thanks for your help!

The problem is probably that httpClient is shutdown before the request is finished and therefore canceled.
Maybe you have a defer { httpClient.shutdonw() } or similar in your code which is executed immediately after you started your request because whenComplete returns immediately. On the other hand, .wait() waits until the request is finished and the httpClient is only shutdown afterwards. Can you show as the code which creates and also eventually shutdowns the HTTPClient?

Similar issue: #477

BTW: If you can use Swift 5.5.2 or higher, I would suggest you to use the new async/await API. Here is an example how to use it:

let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
do {
let request = HTTPClientRequest(url: "https://xkcd.com/info.0.json")
let response = try await httpClient.execute(request, timeout: .seconds(30))
print("HTTP head", response)
let body = try await response.body.collect(upTo: 1024 * 1024) // 1 MB
// we use an overload defined in `NIOFoundationCompat` for `decode(_:from:)` to
// efficiently decode from a `ByteBuffer`
let comic = try JSONDecoder().decode(Comic.self, from: body)
dump(comic)
} catch {
print("request failed:", error)
}
// it is important to shutdown the httpClient after all requests are done, even if one failed
try await httpClient.shutdown()

Hi, ah okay thanks I guess this code works for now.. yeah for some reasons, i'm not able to use async/await for now
Thanks for the help @dnadoba

let future = httpClient.execute(request: request)        
        defer {
            _ = try? future.wait() // --> gotta discard the result here
            try? httpClient.syncShutdown()
        }
        
        httpClient.execute(request: request).whenComplete { result in
            dump(result)
            switch result {
            case .failure(let error):
                print("Error \(error)")
            case .success(let response):
                print("Response: \(response)")
                if response.status == .ok {
                    print("Response: \(response)")
                } else {
                    print("Response is not ok")
                }
            }
        }