I'm trying to make a generic function to handle exceptions to reduce code duplications.
the function is like this:
Future<T> exceptionHandler<T, E extends Exception>({
required ValueGetter<Future<T>> tryBlock,
}) async {
try {
return await tryBlock();
} catch (e) {
if (e is E) {
rethrow;
}
throw E( // Error: E isn't a function
error: e.toString(),
);
}
}
I have created custom exceptions, one of them is called
AuthException
.
This function is intended to be used as follows:
Instead of doing this:
Future<bool> signUp({
required covariant UserModel user,
required String password,
}) async {
try {
// try block code goes here
} catch (e) {
if (e is AuthException) {
rethrow;
}
throw AuthException(
error: e.toString(),
);
}
}
I want to be able to do this:
Future<bool> signUp({
required covariant UserModel user,
required String password,
}) async {
return await exceptionHandler<bool, AuthException>(
tryBlock: () async {
// try block goes here
},
);
}
The AuthException
is defined like this:
class AuthException implements Exception {
final String error;
const AuthException({
required this.error,
});
}
The problem is that I can't throw E
from the exceptionHandler
function because E
is a type and not an object. Is there a way to do this?
CodePudding user response:
You cannot directly construct an instance of a generic type argument. Dart does not consider constructors and static
methods to be part of the class's interface, so the restriction of E extends Exception
does not guarantee that E
has an unnamed constructor that takes a named argument error
.
You instead can have your generic function take a callback that constructs the type you want:
Future<T> exceptionHandler<T, E extends Exception>({
required ValueGetter<Future<T>> tryBlock,
required E Function({String error}) makeException,
}) async {
try {
return await tryBlock();
} catch (e) {
if (e is E) {
rethrow;
}
throw makeException(error: e.toString());
}
}
and then usage would be:
Future<bool> signUp({
required covariant UserModel user,
required String password,
}) async {
return await exceptionHandler(
tryBlock: () async {
// try block goes here
},
makeException: AuthException.new,
);
}
I'll also note that your catch
block will catch everything, including Error
s, which is usually frowned upon, and it loses the stack trace, which will make failures harder to debug. I'd restrict the catch
clause to Exception
s and use Error.throwWithStackTrace
:
Future<T> exceptionHandler<T, E extends Exception>({
required ValueGetter<Future<T>> tryBlock,
required E Function({String error}) makeException,
}) async {
try {
return await tryBlock();
} on Exception catch (e, stackTrace) {
if (e is E) {
rethrow;
}
Error.throwWithStackTrace(
makeException(error: e.toString(),
stackTrace,
);
}
}