avoid_passing_async_when_sync_expected
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.
Why use this rule
Section titled “Why use this rule”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); }});Known limitations
Section titled “Known limitations”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.
Turning this rule off
Section titled “Turning this rule off”This rule is in the opinionated preset, so it is on with
preset: opinionated, or by name:
rules: avoid_passing_async_when_sync_expected: trueTo turn it off again:
rules: avoid_passing_async_when_sync_expected: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Options
Section titled “Options”many_lints: rules: avoid_passing_async_when_sync_expected: ignore_widget_callbacks: true ignored_parameters: [onRetry]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 |
Related rules
Section titled “Related rules”avoid_redundant_async— Flag an async function that never awaits.avoid_future_ignore— Do not silently suppress Future errors with an unexplained ignore call.avoid_nested_futures— Don’t declare Future<Future<T>>.prefer_correct_future_return_type— Expose async results as non-nullable Future values.