I’m encountering an issue with my in-memory caching implementation where fetchValue is not capturing the second callback. My current setup involves fetching data using Apollo GraphQL with Combine's Future, but the second callback is not being triggered as expected.
func myRequest<T, Q: GraphQLQuery>(query: Q) -> Future<Response<T>, CYGErrorType>? where T: RootSelectionSet {
return Future<Response<T>, CYGErrorType> { promise in
guard let futureFetchValue = self.fetchValue(query: query, cachePolicy: self.model.cachePolicy) as Future<Response<T>, CYGErrorType>? else {
promise(.failure(.default))
return
}
futureFetchValue
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
break
case .failure(let error):
promise(.failure(error))
}
}, receiveValue: { result in
print("API>> API>> response")
promise(.success(result))
})
.store(in: &self.cancellable)
}
}
==============
private func fetchValue<T, Query: GraphQLQuery>(query: Query, cachePolicy: CachePolicy) -> Future<Response<T>, CYGErrorType>? where T: RootSelectionSet {
return Future<Response<T>, CYGErrorType> { promise in
print("API>> hit")
apolloClient.fetch(query: query, cachePolicy: cachePolicy) { result in
switch result {
case .success(let graphQLResult):
let validateResponse = graphQLResult.isValid(query: query)
switch validateResponse {
case .success:
guard let data = graphQLResult.data as? T else {
promise(.failure(CYGErrorType.default))
return
}
let response = Response(value: data, response: nil, error: nil)
promise(.success(response))
print("API>> response")
case .failureWithError(let error):
promise(.failure(error))
}
case .failure(let error):
let cygError = self.graphQLErrorParsing(error: error, queryName: Query.operationName)
promise(.failure(cygError))
}
}
}
}
output:
print("API>> hit")
print("API>> response")
print("API>> API>> response")
print("API>> response")
Expected output
print("API>> hit")
print("API>> response")
print("API>> API>> response")
print("API>> response")
print("API>> API>> response")
The second callback from fetchValue is not being captured from myRequest. I’m expecting to see both API responses in the output, but the second one is missing. It seems like Combine
Future
is only support first callback not the second one, but I’m not sure how to properly handle it.
Why is fetchValue not capturing the second callback? How can I ensure that both callbacks are properly captured and handled? Any help or suggestions on how to resolve this issue would be greatly appreciated!
Your code is intimately tied to libraries (the Apollo Client) that that folks answering your question may not be familiar with, and a test environment would require a GraphQL server that they don't have - so answering your question directly will be difficult.
Within the sample you've given, the function myRequest
calls fetchValue
. fetchValue
returns a Future
and myRequest
seems to wrap that Future
up in another Future
that really doesn't serve a purpose apart from echoing the results of 'fetchValue`.
I don't understand why you've done that. It really seems that myRequest
and fetchValue
are equivalent - you should be able to remove myRequest
altogether and simply call fetchValue
.
You imply that apolloClient.fetch
may call its completion callback function more than once for a given query. Is that the case?
If so then as suggested in the comments, you may want to use a PassthroughSubject
(representing the return type as an AnyPublisher
).
A Future
represents a single request that returns a single result at some time in the future. It is a stream that emits one value and then terminates. So it would not be a good model if apolloClient.fetch
is going to return more than one result.
A PassthroughSubject
is a generic stream that can carry multiple values. It will terminate when an error is encountered or when the stream is explicitly finished. (It's not clear from your code how to tell when apolloClient.fetch
is done sending values and the stream should terminate)
Ignoring that detail, what you may need is something like this:
private func fetchValue<T, Query: GraphQLQuery>(query: Query, cachePolicy: CachePolicy) -> AnyPublisher<Response<T>, CYGErrorType>? where T: RootSelectionSet {
let resultSubject = PassthroughSubject<Response<T>, CYGErrorType>()
print("API>> hit")
apolloClient.fetch(query: query, cachePolicy: cachePolicy) { result in
switch result {
case .success(let graphQLResult):
let validateResponse = graphQLResult.isValid(query: query)
switch validateResponse {
case .success:
guard let data = graphQLResult.data as? T else {
resultSubject.send(completion: .failure(CYGErrorType.default))
return
}
let response = Response(value: data, response: nil, error: nil)
resultSubject.send(response)
print("API>> response")
case .failure(let error):
resultSubject.send(completion: .failure(error))
}
case .failure(let error):
resultSubject.send(completion: .failure(error))
}
}
return resultSubject.eraseToAnyPublisher()
}
Here the code creates a PassthroughSubject
and each time the completion handler is called it will emit the value passed to the callback through that subject. At the bottom of the function we convert the subject to an AnyPublisher
since the fact that it's a PassthroughSubject
is an implementation detail that folks outside of this function don't need to know