I tried using your latest solution robert.ryan to adapt Apollo fetch method. The problem is that the continuation inside tryFetch method now leaks when I cancel the request. Here is the code:
actor SendableQueryAdapter {
var state: State = .ready
func fetch<Query: GraphQLQuery>(
query: Query,
client: ApolloClientProtocol,
cachePolicy: CachePolicy,
contextIdentifier: UUID? = nil,
queue: DispatchQueue,
resultHandler: @Sendable @escaping (Result<GraphQLResult<Query.Data>, Error>) -> Void
) {
if case .cancelled = state {
resultHandler(.failure(CancellationError()))
return
}
let cancellable = client.fetch(query: query, cachePolicy: cachePolicy, contextIdentifier: contextIdentifier, queue: queue, resultHandler: resultHandler)
state = .executing(cancellable)
}
func cancel() {
if case .executing(let task) = state {
task.cancel()
}
state = .cancelled
}
}
extension SendableQueryAdapter {
enum State {
case ready
case executing(Apollo.Cancellable)
case cancelled
}
}
extension ApolloClientProtocol {
func tryFetch<Query: GraphQLQuery>(
query: Query,
cachePolicy: CachePolicy,
contextIdentifier: UUID?,
queue: DispatchQueue
) async throws -> Query.Data {
let adapter = SendableQueryAdapter()
return try await withTaskCancellationHandler {
try await withCheckedThrowingContinuation { continuation in
Task {
await adapter.fetch(query: query, client: self, cachePolicy: cachePolicy, queue: queue, resultHandler: { result in
switch result {
case .success(let graphQLResult):
if let data = graphQLResult.data {
continuation.resume(returning: data)
} else if let error = graphQLResult.errors?.first {
continuation.resume(throwing: error)
} else {
continuation.resume(throwing: URLError(.badServerResponse))
}
case .failure(let error):
continuation.resume(throwing: error)
}
})
}
}
} onCancel: {
Task { await adapter.cancel() }
}
}
}
Am I missing something?