Skip to content

avoid_redundant_async

v1.0.0 Warning Async Safety

This rule flags a function marked async whose body never awaits or throws, and whose return paths already produce compatible Future values. Removing async is therefore a cleanup that keeps the code compiling.

The rule is part of the opinionated preset.

async without await is not automatically redundant — it still wraps a raw return value and turns a synchronous throw into an asynchronous error. The rule reports only the case where neither applies: every explicit return already produces a compatible Future, so dropping the keyword changes nothing but the noise.

See also: Dart asynchrony support

The body already hands back a future, so async adds a wrap-and-unwrap round trip and nothing else:

Future<List<String>> loadAll(List<Future<String>> pending) async {
return Future.wait(pending);
}
Future<List<String>> loadAll(List<Future<String>> pending) {
return Future.wait(pending);
}

The same shape shows up most often in a thin delegating method:

// Don't
Future<User> fetchUser(String id) async {
return _api.getUser(id);
}
// Do
Future<User> fetchUser(String id) {
return _api.getUser(id);
}

Note this is only equivalent because there is no await. If you add one — return await _api.getUser(id); — the async is doing real work and the rule stays quiet.

async is not redundant when it is the thing producing the future, or converting a throw into an asynchronous error:

Future<int> count() async => 1; // wraps a raw value
Future<int> mustExist() async {
throw StateError('missing'); // becomes an async error
}
Future<int> lookup(String id) async {
return await _cache.read(id); // has an await
}

An async* stream generator and an @override are both skipped as well — the keyword is load-bearing in the first, and an override’s signature is not this rule’s to rewrite.

To disable this rule:

many_lints.yaml
rules:
avoid_redundant_async: false

To keep the rule on but skip certain paths, use per-rule exclude.