diff --git a/dio/CHANGELOG.md b/dio/CHANGELOG.md index f1ab769e6..7c0b42bb6 100644 --- a/dio/CHANGELOG.md +++ b/dio/CHANGELOG.md @@ -6,6 +6,7 @@ See the [Migration Guide][] for the complete breaking changes list.** ## Unreleased - Fix `receiveTimeout` throws exception after the request has been cancelled. +- Catch sync/async exceptions in interceptors' handlers. ## 5.4.1 diff --git a/dio/lib/src/dio_mixin.dart b/dio/lib/src/dio_mixin.dart index 9ec104f1a..9c5507b36 100644 --- a/dio/lib/src/dio_mixin.dart +++ b/dio/lib/src/dio_mixin.dart @@ -375,29 +375,31 @@ abstract class DioMixin implements Dio { // Convert the request interceptor to a functional callback in which // we can handle the return value of interceptor callback. FutureOr Function(dynamic) requestInterceptorWrapper( - InterceptorSendCallback interceptor, + InterceptorSendCallback cb, ) { return (dynamic incomingState) async { final state = incomingState as InterceptorState; if (state.type == InterceptorResultType.next) { return listenCancelForAsyncTask( requestOptions.cancelToken, - Future(() { - final requestHandler = RequestInterceptorHandler(); - interceptor(state.data as RequestOptions, requestHandler); - return requestHandler.future; + Future(() async { + final handler = RequestInterceptorHandler(); + final callback = cb(state.data as RequestOptions, handler); + if (callback is Future) { + await callback; + } + return handler.future; }), ); - } else { - return state; } + return state; }; } // Convert the response interceptor to a functional callback in which // we can handle the return value of interceptor callback. FutureOr Function(dynamic) responseInterceptorWrapper( - InterceptorSuccessCallback interceptor, + InterceptorSuccessCallback cb, ) { return (dynamic incomingState) async { final state = incomingState as InterceptorState; @@ -405,10 +407,13 @@ abstract class DioMixin implements Dio { state.type == InterceptorResultType.resolveCallFollowing) { return listenCancelForAsyncTask( requestOptions.cancelToken, - Future(() { - final responseHandler = ResponseInterceptorHandler(); - interceptor(state.data as Response, responseHandler); - return responseHandler.future; + Future(() async { + final handler = ResponseInterceptorHandler(); + final callback = cb(state.data as Response, handler); + if (callback is Future) { + await callback; + } + return handler.future; }), ); } else { @@ -420,16 +425,19 @@ abstract class DioMixin implements Dio { // Convert the error interceptor to a functional callback in which // we can handle the return value of interceptor callback. FutureOr Function(Object) errorInterceptorWrapper( - InterceptorErrorCallback interceptor, + InterceptorErrorCallback cb, ) { return (error) { final state = error is InterceptorState ? error : InterceptorState(assureDioException(error, requestOptions)); Future handleError() async { - final errorHandler = ErrorInterceptorHandler(); - interceptor(state.data, errorHandler); - return errorHandler.future; + final handler = ErrorInterceptorHandler(); + final callback = cb(state.data, handler); + if (callback is Future) { + await callback; + } + return handler.future; } // The request has already been cancelled, @@ -437,15 +445,15 @@ abstract class DioMixin implements Dio { if (state.data is DioException && state.data.type == DioExceptionType.cancel) { return handleError(); - } else if (state.type == InterceptorResultType.next || + } + if (state.type == InterceptorResultType.next || state.type == InterceptorResultType.rejectCallFollowing) { return listenCancelForAsyncTask( requestOptions.cancelToken, Future(handleError), ); - } else { - throw error; } + throw error; }; } @@ -495,12 +503,13 @@ abstract class DioMixin implements Dio { future = future.catchError(errorInterceptorWrapper(fun)); } // Normalize errors, converts errors to [DioException]. - return future.then>((data) { + try { + final data = await future; return assureResponse( data is InterceptorState ? data.data : data, requestOptions, ); - }).catchError((Object e) { + } catch (e) { final isState = e is InterceptorState; if (isState) { if (e.type == InterceptorResultType.resolve) { @@ -508,7 +517,7 @@ abstract class DioMixin implements Dio { } } throw assureDioException(isState ? e.data : e, requestOptions); - }); + } } Future> _dispatchRequest(RequestOptions reqOpt) async { diff --git a/dio/lib/src/interceptor.dart b/dio/lib/src/interceptor.dart index bca8981b2..5259e8ba5 100644 --- a/dio/lib/src/interceptor.dart +++ b/dio/lib/src/interceptor.dart @@ -207,19 +207,19 @@ class Interceptor { } /// The signature of [Interceptor.onRequest]. -typedef InterceptorSendCallback = void Function( +typedef InterceptorSendCallback = FutureOr Function( RequestOptions options, RequestInterceptorHandler handler, ); /// The signature of [Interceptor.onResponse]. -typedef InterceptorSuccessCallback = void Function( +typedef InterceptorSuccessCallback = FutureOr Function( Response response, ResponseInterceptorHandler handler, ); /// The signature of [Interceptor.onError]. -typedef InterceptorErrorCallback = void Function( +typedef InterceptorErrorCallback = FutureOr Function( DioException error, ErrorInterceptorHandler handler, ); diff --git a/dio/test/interceptor_test.dart b/dio/test/interceptor_test.dart index b757e4f71..097515ecd 100644 --- a/dio/test/interceptor_test.dart +++ b/dio/test/interceptor_test.dart @@ -318,6 +318,31 @@ void main() { expect(response.data['errCode'], 0); }); + test('Caught exceptions before handler called', () async { + final dio = Dio(); + const errorMsg = 'interceptor error'; + dio.interceptors.add( + InterceptorsWrapper( + // TODO(EVERYONE): Remove the ignorance once we migrated to a higher version of Dart. + // ignore: void_checks + onRequest: (response, handler) { + throw UnsupportedError(errorMsg); + }, + ), + ); + expect( + dio.get('https://www.cloudflare.com'), + throwsA( + isA().having( + (dioException) => dioException.error, + 'Exception', + isA() + .having((e) => e.message, 'message', errorMsg), + ), + ), + ); + }); + group(ImplyContentTypeInterceptor, () { Dio createDio() { final dio = Dio();