void negotiateResponseFromVariants(
            final HttpHost target,
            final HttpRequest request,
            final AsyncEntityProducer entityProducer,
            final AsyncExecChain.Scope scope,
            final AsyncExecChain chain,
            final AsyncExecCallback asyncExecCallback,
            final Collection<CacheHit> variants) {
        final String exchangeId = scope.exchangeId;
        final CancellableDependency operation = scope.cancellableDependency;
        final Map<ETag, CacheHit> variantMap = new HashMap<>();
        for (final CacheHit variant : variants) {
            final ETag eTag = variant.entry.getETag();
            if (eTag != null) {
                variantMap.put(eTag, variant);
            }
        }

        final HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequestFromVariants(
                request,
                variantMap.keySet());

        final Instant requestDate = getCurrentDate();
        chainProceed(conditionalRequest, entityProducer, scope, chain, new AsyncExecCallback() {

            final AtomicReference<AsyncExecCallback> callbackRef = new AtomicReference<>();

            void updateVariantCacheEntry(final HttpResponse backendResponse, final Instant responseDate, final CacheHit match) {
                final HttpCacheContext context = HttpCacheContext.cast(scope.clientContext);
                context.setCacheResponseStatus(CacheResponseStatus.VALIDATED);
                cacheUpdates.getAndIncrement();

                operation.setDependency(responseCache.storeFromNegotiated(
                        match,
                        target,
                        request,
                        backendResponse,
                        requestDate,
                        responseDate,
                        new FutureCallback<CacheHit>() {

                            @Override
                            public void completed(final CacheHit hit) {
                                try {
                                    final SimpleHttpResponse cacheResponse = generateCachedResponse(request, hit.entry, responseDate);
                                    context.setCacheEntry(hit.entry);
                                    triggerResponse(cacheResponse, scope, asyncExecCallback);
                                } catch (final ResourceIOException ex) {
                                    asyncExecCallback.failed(ex);
                                }
                            }

                            @Override
                            public void failed(final Exception ex) {
                                asyncExecCallback.failed(ex);
                            }

                            @Override
                            public void cancelled() {
                                asyncExecCallback.failed(new InterruptedIOException());
                            }

                        }));
            }

            @Override
            public AsyncDataConsumer handleResponse(
                    final HttpResponse backendResponse,
                    final EntityDetails entityDetails) throws HttpException, IOException {
                final Instant responseDate = getCurrentDate();
                final AsyncExecCallback callback;
                if (backendResponse.getCode() != HttpStatus.SC_NOT_MODIFIED) {
                    callback = new BackendResponseHandler(target, request, requestDate, responseDate, scope, asyncExecCallback);
                } else {
                    final ETag resultEtag = ETag.get(backendResponse);
                    if (resultEtag == null) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("{} 304 response did not contain ETag", exchangeId);
                        }
                        callback = new AsyncExecCallbackWrapper(() -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback), asyncExecCallback::failed);
                    } else {
                        final CacheHit match = variantMap.get(resultEtag);
                        if (match == null) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("{} 304 response did not contain ETag matching one sent in If-None-Match", exchangeId);
                            }
                            callback = new AsyncExecCallbackWrapper(() -> callBackend(target, request, entityProducer, scope, chain, asyncExecCallback), asyncExecCallback::failed);
                        } else {
                            if (HttpCacheEntry.isNewer(match.entry, backendResponse)) {
                                final HttpRequest unconditional = conditionalRequestBuilder.buildUnconditionalRequest(
                                        BasicRequestBuilder.copy(request).build());
                                callback = new AsyncExecCallbackWrapper(() -> callBackend(target, unconditional, entityProducer, scope, chain, asyncExecCallback), asyncExecCallback::failed);
                            } else {
                                callback = new AsyncExecCallbackWrapper(() -> updateVariantCacheEntry(backendResponse, responseDate, match), asyncExecCallback::failed);
                            }
                        }
                    }
                }
                callbackRef.set(callback);
                return callback.handleResponse(backendResponse, entityDetails);
            }

            @Override
            public void handleInformationResponse(final HttpResponse response) throws HttpException, IOException {
                final AsyncExecCallback callback = callbackRef.getAndSet(null);
                if (callback != null) {
                    callback.handleInformationResponse(response);
                } else {
                    asyncExecCallback.handleInformationResponse(response);
                }
            }

            @Override
            public void completed() {
                final AsyncExecCallback callback = callbackRef.getAndSet(null);
                if (callback != null) {
                    callback.completed();
                } else {
                    asyncExecCallback.completed();
                }
            }

            @Override
            public void failed(final Exception cause) {
                final AsyncExecCallback callback = callbackRef.getAndSet(null);
                if (callback != null) {
                    callback.failed(cause);
                } else {
                    asyncExecCallback.failed(cause);
                }
            }

        });

    }
Request executor in the request execution chain that is responsible for
transparent client-side caching.
<p>
The current implementation is conditionally
compliant with HTTP/1.1 (meaning all the MUST and MUST NOTs are obeyed),
although quite a lot, though not all, of the SHOULDs and SHOULD NOTs
are obeyed too.

@since 5.0
httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java