fetch<T> method

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

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;
    }
  }

  // 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();
            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();
            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();
        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);
  }
}