Coder Social home page Coder Social logo

Comments (19)

AlexV525 avatar AlexV525 commented on July 22, 2024 1

cc @seunghwanly to provide a detailed explanation.

from dio.

AlexV525 avatar AlexV525 commented on July 22, 2024 1

@AlexV525 Umm .. should I provide more clearer example than the current one? for clear understanding

Yeah feel free to request changes and then we can discuss

from dio.

ykaito21 avatar ykaito21 commented on July 22, 2024 1

@seunghwanly Thank you for your help. For me, your example is clear enough, and I understand the flow, but my point is what happen if final originResult = await dio.fetch(options..path += '&pass=true'); fails, and how to handle it.

  • Should I use different dio instance for retry the original request onError?, but using different instances for the same request requires the same configuration for both instances?
  • Should I call retried original request with in try-catch block or not?

final originResult = await dio.fetch(options..path += '&pass=true');
if (originResult.statusCode != null &&
originResult.statusCode! ~/ 100 == 2) {
return handler.resolve(originResult);
}

@ykaito21's example at the top, using same instance might cause infinite loop and keep calling this cycle.

onRequest is called
onError is called

check on debug mode (look for hashCode of dio) and see if same instance is requested all the time.

indeed, it is the same instance

from dio.

seunghwanly avatar seunghwanly commented on July 22, 2024 1

@seunghwanly, thanks for the detailed explanation and updated example.

I have a question about your example, I don't see a case in onError for an automatic retry with a new token, if I wanted to add that, could I just create a new dio in onError and use that like tokenDio?

  /// Add `onError` interceptor to request new CSRF token
  dio.interceptors.add(
    QueuedInterceptorsWrapper(
      /// Request new CSRF token
      /// if the response status code is `401`
      onError: (error, handler) async {
        log('Error Detected: ${error.message}');


        if (error.response == null) return handler.next(error);


        if (error.response?.statusCode == 401) {
          try {
            final tokenDio = Dio(
              BaseOptions(baseUrl: error.requestOptions.baseUrl),
            );


            /// Generate CSRF token
            ///
            /// This is a MOCK REQUEST to generate a CSRF token.
            /// In a real-world scenario, this should be generated by the server.
            final result = await tokenDio.post(
              '/response-headers',
              queryParameters: {
                _headerKey: '94d6d1ca-fa06-468f-a25c-2f769d04c26c',
              },
            );


            if (result.statusCode == null || result.statusCode! ~/ 100 != 2) {
              throw DioException(requestOptions: result.requestOptions);
            }


            final updatedToken = result.headers.value(_headerKey);
            if (updatedToken == null) throw ArgumentError.notNull(_headerKey);


            cachedCSRFToken = updatedToken;
           
             // Can I do like this? or is there better way?
            final retryDio = Dio(
              BaseOptions(baseUrl: error.requestOptions.baseUrl),
            );
            final requestOptions = err.requestOptions;
            requestOptions.headers['token'] = '$updatedToken';
            final res = retryDio.fetch(requestOptions);

            return handler.resolve(res);
          } on DioException catch (e) {
            return handler.reject(e);
          }
        }
      },
    ),
  );

Yes, I wrote the example for QueuedInterceptorsWrapper to see callbacks handled in a row. And as it said before

The example does not contain retries, so 2 instances were used for the flow. More instances do not mean more resource costs because it just acts as a manager. Better start to use packages like dio_smart_retry.

use another dio to manage retry or other requests to handle exception cases. Here is an example for a retry interceptor.

from dio.

AlexV525 avatar AlexV525 commented on July 22, 2024 1

@AlexV525

I updated example/lib/queued_interceptor_crsftoken.dart here. It seems to be more clear than before like handling errors and queued interceptors.

Outputs 스크린샷 2024-03-07 오전 2 02 05

@seunghwanly Hi sorry for missing the thread. Could you submit the pull request?

from dio.

LtotheWT avatar LtotheWT commented on July 22, 2024

+1 any update one this ?

from dio.

AlexV525 avatar AlexV525 commented on July 22, 2024

You are reusing the same Dio instance in callbacks, consider adding another instance for retries which could avoid deadlocks in the most of cases.

from dio.

ykaito21 avatar ykaito21 commented on July 22, 2024

You are reusing the same Dio instance in callbacks, consider adding another instance for retries which could avoid deadlocks in the most of cases.

You mean I have to use 3 different Dio instances on refresh token and retry? But the queued_interceptor_crsftoken example uses the same instance on retry.

from dio.

AlexV525 avatar AlexV525 commented on July 22, 2024

You mean I have to use 3 different Dio instances on refresh token and retry? But the queued_interceptor_crsftoken example uses the same instance on retry.

The example does not contain retries, so 2 instances were used for the flow. More instances do not mean more resource costs because it just acts as a manager. Better start to use packages like dio_smart_retry.

from dio.

ykaito21 avatar ykaito21 commented on July 22, 2024

The example does not contain retries, so 2 instances were used for the flow. More instances do not mean more resource costs because it just acts as a manager. Better start to use packages like dio_smart_retry.

Isn't that retry? final originResult = await dio.fetch(options..path += '&pass=true');.
I don't understand the difference from my case.

from dio.

seunghwanly avatar seunghwanly commented on July 22, 2024

cc @seunghwanly to provide a detailed explanation.

In my example, I've used 2 different instances for Dio

  • dio: handles main request and response
  • tokenDio: manages token, when token has expired or request failed with 401 status code

/// assume receiving the token has no errors
/// to check `null-safety` and error handling
/// please check inside the [onRequest] closure
final tokenResult = await tokenDio.get('/token');
/// update [csrfToken]
/// assume `token` is in response body
final body = jsonDecode(tokenResult.data) as Map<String, dynamic>?;
options.headers['csrfToken'] = csrfToken = body!['data']['token'];

on onError callback, tokenDio is used for token refresh and adds new 'token' to original dio(dio: L7) as you can see at the last line (L60)

then when its token(csrfToken) has been updated, which I pretended as not null(!= null). I tried a new request with original dio(dio: L7) (new token added).

And for the result, I just added handler when it has succeeded (not for error) like below.

if (originResult.statusCode != null &&
originResult.statusCode! ~/ 100 == 2) {
return handler.resolve(originResult);
}


@ykaito21's example at the top, using same instance might cause infinite loop and keep calling this cycle.

onRequest is called
onError is called

check on debug mode (look for hashCode of dio) and see if same instance is requested all the time.

from dio.

seunghwanly avatar seunghwanly commented on July 22, 2024

@AlexV525 Umm .. should I provide more clearer example than the current one? for clear understanding

from dio.

seunghwanly avatar seunghwanly commented on July 22, 2024

Should I use different dio instance for retry the original request onError?, but using different instances for the same request requires the same configuration for both instances?

If we need updated data from the server, I'd rather use another instance for the request, and for the same configuration I made an example like the one below.

tokenDio.options = dio.options;

Or else I might not use a different instance to request a retrial. In that case things like changing the host from cached to its origin.

const originHost = 'origin.com';
const cachedHost = 'cached.origin.com';

final originDio = Dio();

... // [originDio]'s interceptor

  onError: (error, handler) async {
    /// Pretend we have an extension called [isTimeout] 
    /// returns `true` when the request past 2 min.
    if (error.isTimeout) {
      /// Set new host
      dio.options.baseUrl = 'https://$cachedHost';
      final result = await dio.fetch(dio.options);
      
      /// handle result
      // TODO
    }
  }
  

Should I call retried original request with in try-catch block or not?

This depends on your code style. I sometimes use the try-catch block to handle specific status codes like 401, 403, 500, and so on. Or handling on 'Presentation Layer' like using Provider, BLoC, or RiverPod.

I hope my answer was helpful enough. Please let me know if you need any further assistance. :)

If we use the same instance in the onError callback and try to catch an error within the try-catch block, we cannot reach the catch block at all.. because the dio will keep rolling between onRequest and onError's try block

P.S. I will soon request changes for the csrfToken example.

from dio.

kuhnroyal avatar kuhnroyal commented on July 22, 2024

I have a token refresh interceptor and now that I read this, I think I am actually seeing a similar behavior.
Need to look into this when I find some time.

from dio.

ykaito21 avatar ykaito21 commented on July 22, 2024

If we use the same instance in the onError callback and try to catch an error within the try-catch block, we cannot reach the catch block at all.. because the dio will keep rolling between onRequest and onError's try block

You mean if I use the same instance in the onError callback, it doesn't matter to use try-catch or not because it doesn't reach to that catch block if it fails. Isn't that the problem?
And if I use the same instance in the onError callback, that dio should not or cannot throw an Exception because if it does, it will keep rolling between onRequest and onError's try block?

Should I call retried original request with in try-catch block or not?

This depends on your code style. I sometimes use the try-catch block to handle specific status codes like 401, 403, 500, and so on. Or handling on 'Presentation Layer' like using Provider, BLoC, or RiverPod.

but if I use dio(different instances) in the onError, and it throws an Exception without try-catch block or catchError, it will cause Unhandled exception. Is this expected?

class ErrorInterceptor extends QueuedInterceptor {
  final Dio dio;
  final Dio dio2;

  ErrorInterceptor(this.dio, this.dio2);

  @override
  Future<void> onError(
      DioException err, ErrorInterceptorHandler handler) async {
    print('onError is called');
    try {
      // Without try-catch, it will throw unhandled exception
      await dio2.fetch(err.requestOptions);
    } catch (e) {
      print('onError is called again');
    }
    handler.next(err);
  }
}

void main() async {
  var dio = Dio();
  var dio2 = Dio();

  // Add the custom interceptor
  dio.interceptors.addAll([
    ErrorInterceptor(dio, dio2),
  ]);

  // Making a GET request
  try {
    print('Making a GET request...');
    Response response =
        await dio.get('https://example.com/this-does-not-exist');
    print(response.data);
  } catch (e) {
    print("Final error: $e");
  }
}

Really appreciate your assistance!

from dio.

seunghwanly avatar seunghwanly commented on July 22, 2024

@ykaito21

You mean if I use the same instance in the onError callback, it doesn't matter to use try-catch or not because it doesn't reach to that catch block if it fails. Isn't that the problem?
And if I use the same instance in the onError callback, that dio should not or cannot throw an Exception because if it does, it will keep rolling between onRequest and onError's try block?

Yes, it create a cycle like this.

dio (onRequest) → Requested → Error → dio (onError) → dio (onRequest) → Requested → Error → dio (onError) → ...

Isn't that the problem?

the actual outputs is just like as what you added as interceptors.addAll()

but if I use dio(different instances) in the onError, and it throws an Exception without try-catch block or catchError, it will cause Unhandled exception. Is this expected?

Yes, 'onError is called again' will be printed as you expected 👍

Control with handler's method

  • next
  • resolve
  • reject
    that might be helpful

from dio.

seunghwanly avatar seunghwanly commented on July 22, 2024

@AlexV525

I updated example/lib/queued_interceptor_crsftoken.dart here. It seems to be more clear than before like handling errors and queued interceptors.

Outputs
Outputs

from dio.

ykaito21 avatar ykaito21 commented on July 22, 2024

@seunghwanly, thanks for the detailed explanation and updated example.

I have a question about your example, I don't see a case in onError for an automatic retry with a new token, if I wanted to add that, could I just create a new dio in onError and use that like tokenDio?

  /// Add `onError` interceptor to request new CSRF token
  dio.interceptors.add(
    QueuedInterceptorsWrapper(
      /// Request new CSRF token
      /// if the response status code is `401`
      onError: (error, handler) async {
        log('Error Detected: ${error.message}');


        if (error.response == null) return handler.next(error);


        if (error.response?.statusCode == 401) {
          try {
            final tokenDio = Dio(
              BaseOptions(baseUrl: error.requestOptions.baseUrl),
            );


            /// Generate CSRF token
            ///
            /// This is a MOCK REQUEST to generate a CSRF token.
            /// In a real-world scenario, this should be generated by the server.
            final result = await tokenDio.post(
              '/response-headers',
              queryParameters: {
                _headerKey: '94d6d1ca-fa06-468f-a25c-2f769d04c26c',
              },
            );


            if (result.statusCode == null || result.statusCode! ~/ 100 != 2) {
              throw DioException(requestOptions: result.requestOptions);
            }


            final updatedToken = result.headers.value(_headerKey);
            if (updatedToken == null) throw ArgumentError.notNull(_headerKey);


            cachedCSRFToken = updatedToken;
           
             // Can I do like this? or is there better way?
            final retryDio = Dio(
              BaseOptions(baseUrl: error.requestOptions.baseUrl),
            );
            final requestOptions = err.requestOptions;
            requestOptions.headers['token'] = '$updatedToken';
            final res = retryDio.fetch(requestOptions);

            return handler.resolve(res);
          } on DioException catch (e) {
            return handler.reject(e);
          }
        }
      },
    ),
  );

from dio.

seunghwanly avatar seunghwanly commented on July 22, 2024

@AlexV525
I updated example/lib/queued_interceptor_crsftoken.dart here. It seems to be more clear than before like handling errors and queued interceptors.
Outputs 스크린샷 2024-03-07 오전 2 02 05

@seunghwanly Hi sorry for missing the thread. Could you submit the pull request?

Thanks, I opened #2128

from dio.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.