fetch<T> method

  1. @override
Future<Response<T>> fetch<T>(
  1. RequestOptions requestOptions
)
inherited

The eventual method to submit requests. All callers for requests should eventually go through this method.

Implementation

@override
Future<Response<T>> fetch<T>(RequestOptions requestOptions) async {
  if (T != dynamic &&
      !(requestOptions.responseType == ResponseType.bytes ||
          requestOptions.responseType == ResponseType.stream)) {
    if (T == String) {
      requestOptions.responseType = ResponseType.plain;
    } else {
      requestOptions.responseType = ResponseType.json;
    }
  }

  // Create an error zone that catches uncaught async errors from
  // interceptor callbacks. This prevents requests from hanging when
  // an async interceptor throws without calling the handler.
  // Synchronous exceptions are not caught here and propagate normally.
  // If the handler was already completed (e.g. a fire-and-forget future
  // inside the callback failed), forward the error to the parent zone
  // so it isn't silently swallowed.
  Zone createInterceptorZone(
    _BaseHandler handler,
    void Function(Object error, StackTrace stackTrace) onUncaughtError,
  ) {
    return Zone.current.fork(
      specification: ZoneSpecification(
        handleUncaughtError: (self, parent, zone, error, stackTrace) {
          if (!handler.isCompleted) {
            onUncaughtError(error, stackTrace);
          } else {
            parent.handleUncaughtError(zone, error, stackTrace);
          }
        },
      ),
    );
  }

  // Convert the request interceptor to a functional callback in which
  // we can handle the return value of interceptor callback.
  FutureOr Function(dynamic) requestInterceptorWrapper(
    InterceptorSendCallback cb,
  ) {
    return (dynamic incomingState) {
      final state = incomingState as InterceptorState;
      if (state.type == InterceptorResultType.next) {
        return listenCancelForAsyncTask(
          requestOptions.cancelToken,
          Future(() async {
            final handler = RequestInterceptorHandler();
            createInterceptorZone(
              handler,
              (error, stackTrace) => handler.reject(
                assureDioException(error, requestOptions, stackTrace),
                true,
              ),
            ).run(() => cb(state.data as RequestOptions, handler));
            return handler.future;
          }),
        );
      }
      return state;
    };
  }

  // Convert the response interceptor to a functional callback in which
  // we can handle the return value of interceptor callback.
  FutureOr<dynamic> Function(dynamic) responseInterceptorWrapper(
    InterceptorSuccessCallback cb,
  ) {
    return (dynamic incomingState) {
      final state = incomingState as InterceptorState;
      if (state.type == InterceptorResultType.next ||
          state.type == InterceptorResultType.resolveCallFollowing) {
        return listenCancelForAsyncTask(
          requestOptions.cancelToken,
          Future(() async {
            final handler = ResponseInterceptorHandler();
            createInterceptorZone(
              handler,
              (error, stackTrace) => handler.reject(
                assureDioException(error, requestOptions, stackTrace),
                true,
              ),
            ).run(() => cb(state.data as Response, handler));
            return handler.future;
          }),
        );
      }
      return state;
    };
  }

  // Convert the error interceptor to a functional callback in which
  // we can handle the return value of interceptor callback.
  FutureOr<dynamic> Function(Object) errorInterceptorWrapper(
    InterceptorErrorCallback cb,
  ) {
    return (dynamic error) {
      final state = error is InterceptorState
          ? error
          : InterceptorState(assureDioException(error, requestOptions));
      Future<InterceptorState> handleError() async {
        final handler = ErrorInterceptorHandler();
        createInterceptorZone(
          handler,
          (error, stackTrace) => handler.next(
            assureDioException(error, requestOptions, stackTrace),
          ),
        ).run(() => cb(state.data, handler));
        return handler.future;
      }

      // The request has already been cancelled,
      // there is no need to listen for another cancellation.
      if (state.data is DioException &&
          state.data.type == DioExceptionType.cancel) {
        return handleError();
      }
      if (state.type == InterceptorResultType.next ||
          state.type == InterceptorResultType.rejectCallFollowing) {
        return listenCancelForAsyncTask(
          requestOptions.cancelToken,
          Future(handleError),
        );
      }
      throw error;
    };
  }

  // Build a request flow in which the processors(interceptors)
  // execute in FIFO order.
  Future<dynamic> future = Future<dynamic>(
    () => InterceptorState(requestOptions),
  );

  // Add request interceptors into the request flow.
  for (final interceptor in interceptors) {
    final fun = interceptor is QueuedInterceptor
        ? interceptor._handleRequest
        : interceptor.onRequest;
    future = future.then(requestInterceptorWrapper(fun));
  }

  // Add dispatching callback into the request flow.
  future = future.then(
    requestInterceptorWrapper((
      RequestOptions reqOpt,
      RequestInterceptorHandler handler,
    ) async {
      requestOptions = reqOpt;
      try {
        final value = await _dispatchRequest<T>(reqOpt);
        handler.resolve(value, true);
      } on DioException catch (e) {
        handler.reject(e, true);
      }
    }),
  );

  // Add response interceptors into the request flow
  for (final interceptor in interceptors) {
    final fun = interceptor is QueuedInterceptor
        ? interceptor._handleResponse
        : interceptor.onResponse;
    future = future.then(responseInterceptorWrapper(fun));
  }

  // Add error handlers into the request flow.
  for (final interceptor in interceptors) {
    final fun = interceptor is QueuedInterceptor
        ? interceptor._handleError
        : interceptor.onError;
    future = future.catchError(errorInterceptorWrapper(fun));
  }
  // Normalize errors, converts errors to [DioException].
  try {
    final data = await future;
    return assureResponse<T>(
      data is InterceptorState ? data.data : data,
      requestOptions,
    );
  } catch (e) {
    final isState = e is InterceptorState;
    if (isState) {
      if (e.type == InterceptorResultType.resolve) {
        return assureResponse<T>(e.data, requestOptions);
      }
    }
    throw assureDioException(isState ? e.data : e, requestOptions);
  }
}