Skip to content

avoid_passing_async_when_sync_expected

v1.0.0WarningConfigurableAsync Safety

This rule flags an async closure passed to a parameter typed void Function(...). The returned Future is assigned to void and dropped: nothing awaits the work, and any error inside it escapes as an unhandled async error.

void is the one return type that both accepts an async closure and silently discards what it produces. The call site compiles, the callback runs, and the caller moves on immediately — before the awaited work has finished.

The more damaging half is error handling. An exception thrown inside a dropped future never reaches the caller’s try/catch; it surfaces as an unhandled async error, usually as console noise far from the code that caused it.

This is deliberately the only shape reported. For any other return type the analyzer already rejects the argument — Future<int> Function() is not assignable to int Function() — so there is nothing left for a lint to add. dynamic and Object returns accept the future as a value the callee can still store or await, so they are not reported either.

See also: Dart: asynchronous programming, unawaited_futures

void schedule(void Function() task) => task();
schedule(() async {
await save(); // nothing awaits this; a failure becomes unhandled
});

Change the parameter so the future has somewhere to go:

Future<void> schedule(Future<void> Function() task) => task();
schedule(() async {
await save();
});

When the callback genuinely is fire-and-forget, handle errors inside it so nothing escapes:

schedule(() async {
try {
await save();
} catch (e, st) {
reportError(e, st);
}
});

Only arguments are checked. A closure assigned to a variable of type void Function() is not reported — the same hazard exists, but the assignment is explicit enough that the author chose the type deliberately.

Flutter’s onPressed, onTap and similar handlers are void-returning by design and are idiomatically given async bodies. They are ignored by default. Set ignore_widget_callbacks: false if your project has an error boundary for those futures and wants to audit every fire-and-forget handler.

This rule is in the opinionated preset, so it is on with preset: opinionated, or by name:

many_lints.yaml
rules:
avoid_passing_async_when_sync_expected: true

To turn it off again:

many_lints.yaml
rules:
avoid_passing_async_when_sync_expected: false

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

analysis_options.yaml
many_lints:
rules:
avoid_passing_async_when_sync_expected:
ignore_widget_callbacks: true
ignored_parameters: [onRetry]
Option Type Default Description
ignore_widget_callbacks bool true Skip the common Flutter handlers (onPressed, onTap, onLongPress, onChanged, onSubmitted, onDoubleTap, onRefresh, onSaved, listener)
ignored_parameters list of strings [] Additional parameter names to skip, by name