avoid-uncaught-future-errors

Path
lint/correctness/avoid-uncaught-future-errors
Default
On (recommended)
Origin
Falcon
Rule
151 of 512
recommended

Flags local futures first awaited after another await inside a try/catch.

A future can complete with an unhandled error while execution is suspended, before the later await attaches the surrounding catch's error handler. Await it immediately or attach an error handler before the first async gap.

This conservative analysis follows straight-line statements directly in a try body with at least one catch. It tracks resolved Future-returning calls and constructors assigned to locals, and reports the later awaited local, not discarded futures. Unknown base types and FutureOr are excluded. Early catchError, onError, and then(onError:) calls are exempt, including handlers attached in a separate statement or initializer. Parentheses are transparent. Branches, loops, nested blocks, local declarations of functions, and other expression statements end tracking: no control-flow or alias assumptions are made across them. Late and multi-variable declarations are excluded. Nested functions and shadowed locals cannot consume an outer candidate. Only root await expressions in expression, return, and local-variable statements count as gaps; awaits nested in other expressions are outside the matching limits. A reference inside a composite await operand conservatively consumes that candidate, covering combinators and forwarding methods without alias guesses. A handler protects only the call it terminates, not subsequent chained calls. When intermediate types are unavailable, only SDK Future method chains (then/catchError/onError/timeout/whenComplete) on a proven Future base qualify.

Invalid

example.dartdart
import 'dart:async';

Future<int> start() async => 1;
Future<void> gap() async {}

Future<void> inferredLocal() async {
  try {
    final pending = start();
    await gap();
    await pending;
  } catch (_) {}
}

Future<void> explicitLocal() async {
  try {
    Future<int> pending = start();
    await gap();
    await pending;
  } catch (_) {}
}

Future<void> parenthesized() async {
  try {
    final pending = (start());
    await (gap());
    await (pending);
  } catch (_) {}
}

Future<void> initializerGap() async {
  try {
    final pending = start();
    final result = await start();
    await pending;
  } catch (_) {}
}

Future<int> returnAwait() async {
  try {
    final pending = start();
    await gap();
    return await pending;
  } catch (_) {
    return 0;
  }
}

Future<void> multiplePending() async {
  try {
    final first = start();
    final second = start();
    await gap();
    await first;
    await second;
  } catch (_) {}
}

Future<void> lateHandler() async {
  try {
    final pending = start();
    await gap();
    pending.catchError((Object error) => 0);
    await pending;
  } catch (_) {}
}

Future<void> laterChainCanFail() async {
  try {
    final pending = start().catchError((Object error) => 0).timeout(const Duration(seconds: 1));
    await gap();
    await pending;
  } catch (_) {}
}

Future<int> withRetry(Future<int> pending) => pending;

Future<void> uncalledClosureCannotConsume() async {
  try {
    final pending = start();
    final retry = () async => await withRetry(pending);
    await gap();
    await pending;
  } catch (_) {}
}
This future is awaited after an async gap; await it immediately or attach an error handler first.
9 await gap();
10 await pending;
This future is awaited after an async gap; await it immediately or attach an error handler first.
17 await gap();
18 await pending;
This future is awaited after an async gap; await it immediately or attach an error handler first.
25 await (gap());
26 await (pending);
This future is awaited after an async gap; await it immediately or attach an error handler first.
33 final result = await start();
34 await pending;
This future is awaited after an async gap; await it immediately or attach an error handler first.
41 await gap();
42 return await pending;
This future is awaited after an async gap; await it immediately or attach an error handler first.
52 await gap();
53 await first;
This future is awaited after an async gap; await it immediately or attach an error handler first.
53 await first;
54 await second;
This future is awaited after an async gap; await it immediately or attach an error handler first.
62 pending.catchError((Object error) => 0);
63 await pending;
This future is awaited after an async gap; await it immediately or attach an error handler first.
70 await gap();
71 await pending;
This future is awaited after an async gap; await it immediately or attach an error handler first.
81 await gap();
82 await pending;

Valid

example.dartdart
import 'dart:async';

Future<int> start() async => 1;
Future<void> gap() async {}

Future<void> immediatelyAwaited() async {
  try {
    final pending = start();
    await pending;
    await gap();
    await pending;
  } catch (_) {}
}

Future<void> initializerHandler() async {
  try {
    final pending = start().catchError((Object error) => 0);
    await gap();
    await pending;
  } catch (_) {}
}

Future<void> typedHandler() async {
  try {
    final pending = start().onError<Exception>((error, stack) => 0);
    await gap();
    await pending;
  } catch (_) {}
}

Future<void> thenHandler() async {
  try {
    final pending = start().then((value) => value, onError: (Object error) => 0);
    await gap();
    await pending;
  } catch (_) {}
}

Future<void> separateHandler() async {
  try {
    final pending = start();
    pending.catchError((Object error) => 0);
    await gap();
    await pending;
  } catch (_) {}
}

Future<void> handlerInitializer() async {
  try {
    final pending = start();
    final handled = pending.catchError((Object error) => 0);
    await gap();
    await pending;
    await handled;
  } catch (_) {}
}

Future<void> dynamicType(dynamic Function() create) async {
  try {
    final pending = create();
    await gap();
    await pending;
  } catch (_) {}
}

Future<void> noCatch() async {
  final pending = start();
  await gap();
  await pending;
}

Future<void> finallyOnly() async {
  try {
    final pending = start();
    await gap();
    await pending;
  } finally {}
}

Future<void> discarded() async {
  try {
    final pending = start();
    await gap();
  } catch (_) {}
}

Future<void> nestedFunction() async {
  try {
    final pending = start();
    Future<void> later() async {
      await gap();
      await pending;
    }
    await pending;
  } catch (_) {}
}

Future<void> shadowed() async {
  try {
    final pending = start();
    {
      final pending = start();
      await pending;
    }
    await pending;
  } catch (_) {}
}

Future<void> reassigned() async {
  try {
    var pending = start();
    await gap();
    pending = start();
    await pending;
  } catch (_) {}
}

Future<void> aliased() async {
  try {
    final pending = start();
    final alias = pending;
    alias.catchError((Object error) => 0);
    await gap();
    await pending;
  } catch (_) {}
}

Future<void> lazyInitialization() async {
  try {
    late final pending = start();
    await gap();
    await pending;
  } catch (_) {}
}

Future<void> multipleDeclarators() async {
  try {
    Future<int> first = start(), second = start();
    await gap();
    await first;
    await second;
  } catch (_) {}
}

Future<void> aggregateAwait() async {
  try {
    final first = start();
    final second = start();
    await Future.wait([first, second]);
    await first;
    await second;
  } catch (_) {}
}

Future<void> forwardingAwait() async {
  try {
    final pending = start();
    await pending.timeout(const Duration(seconds: 1));
    await pending;
  } catch (_) {}
}

Future<void> compositeHandlerStatement() async {
  try {
    final pending = start();
    pending.then((value) => value).catchError((Object error) => 0);
    await gap();
    await pending;
  } catch (_) {}
}

Future<void> compositeHandlerInitializer() async {
  try {
    final pending = start();
    final handled = pending.then((value) => value).catchError((Object error) => 0);
    await gap();
    await pending;
    await handled;
  } catch (_) {}
}

FutureOr<int> maybeStart() => 1;
Future<void> futureOrIsNotProof() async {
  try {
    final pending = maybeStart();
    await gap();
    await pending;
  } catch (_) {}
}

Future<void> conditionalGap(bool condition) async {
  try {
    final pending = start();
    if (condition) {
      await gap();
    }
    await pending;
  } catch (_) {}
}

Future<void> loopBoundary(List<int> values) async {
  try {
    final pending = start();
    for (final value in values) {
      await gap();
    }
    await pending;
  } catch (_) {}
}

Configure avoid-uncaught-future-errors

Set its severity in falcon.json. See the configuration guide.

falcon.jsonjson
{
  "linter": {
    "rules": {
      "correctness": {
        "avoid-uncaught-future-errors": "error"
      }
    }
  }
}

Or silence one occurrence with a suppression comment; the reason is required.

example.dartdart
// falcon-ignore lint/correctness/avoid-uncaught-future-errors: <reason>