Irbisa · Cheatsheet

Async & Futures

Future, async/await, then/catchError, FutureBuilder

Junior Developer · 22 questions

  1. 01

    What is a Future, how do async and await work, and how do you handle errors from a Future?

    A Future<T> is a value, or an error, that will arrive later.

    Future<String> fetchUser(int id) async {  await Future<void>.delayed(const Duration(seconds: 1)); // stands in for a network call  if (id <= 0) throw ArgumentError.value(id, 'id');  return 'User #$id';}…
  2. 02

    Does async/await run your code on another thread?

    No. await only lets the event loop do other work while something outside your isolate is pending: a network call, a file read, a timer.

  3. 03

    What does this code print, and which part of load() runs before main() moves on?

    Future<void> load() async {  print('2');  await Future<void>.delayed(Duration.zero);  print('4');} void main() {  print('1');  load();  print('3');}

    It prints 1 2 3 4.

  4. 04

    What is the difference between .then() chaining and async/await, and how does whenComplete() differ from then()?

    Both build the same Future, so it is a readability choice, and async/await usually wins.

    // then-chainFuture<Profile> loadChain(String id) {  return api      .user(id)      .then((u) => api.profile(u.id))      .catchError((Object e) => Profile.empty());…
  5. 05

    What is Timer / Timer.periodic, how do you cancel it, and what happens if you forget to cancel it in dispose()?

    Timer(duration, callback) runs a callback once after a delay, and Timer.periodic runs it repeatedly; both return a Timer you stop with cancel().

    class _ClockState extends State<Clock> {  Timer? _ticker;  Timer? _debounce;  DateTime _now = DateTime.now();   @override…
  6. 06

    What does FutureBuilder do, and why should you not create its Future inside build()?

    FutureBuilder rebuilds part of the UI from a Future's state: waiting, done with data, or done with an error.

    class ProfilePage extends StatefulWidget {  const ProfilePage({super.key, required this.id});  final String id;   @override  State<ProfilePage> createState() => _ProfilePageState();…
  7. 07

    Why should an async function return Future<void> instead of void?

    Because with void the caller gets nothing to await: it cannot wait for the work to finish, and errors thrown inside skip the caller's try/catch and surface as uncaught errors.

    // wrong: callers cannot wait for it or catch its errorsvoid saveDraftVoid() async {  await storage.write(draft);} // right…
  8. 08

    What happens if you forget to await a Future, and when is fire-and-forget with unawaited() acceptable?

    The function carries on before the work finishes, and an error from that Future has no listener, so it becomes an uncaught async error instead of reaching your try/catch.

    Future<void> onSave() async {  await repo.save(draft); // right: wait for what the next line depends on  unawaited(analytics.track("saved").catchError(logDrop)); // fire-and-forget, errors handled  // wrong: repo.save(draft); close(); // close() runs before the save finishes}
  9. 09

    Find the bug: the "All photos synced" toast appears while photos are still uploading.

    Future<void> syncPhotos(List<Photo> photos) async {  photos.forEach((photo) async {    await api.upload(photo);    await db.markSynced(photo.id);  });  showToast('All ${photos.length} photos synced');}

    forEach ignores what its callback returns, so the Futures of the async closure are dropped: syncPhotos shows the toast and completes before any upload ends, and a failed upload becomes an uncaught error instead of reaching the caller.

  10. 10

    How do you run several Futures concurrently, and what happens when one of them fails?

    Start all the Futures first and wait for them together: Future.wait([...]), or in Dart 3 a record, await (fetchUser(), fetchPrefs()).wait.

    Future<void> load(String id) async {  // Same type: one list, results in the original order  final pages = await Future.wait([loadPage(1), loadPage(2), loadPage(3)]);   // Per-future fallback: one failure does not lose the other result  final counts = await Future.wait([…
  11. 11

    How does Dart's event loop work, and what is the difference between the microtask queue and the event queue?

    Each isolate runs one thread with an event loop and two queues.

  12. 12

    In what order does this code print, and why?

    void main() {  print('A');  Future(() => print('B'));  Future.delayed(Duration.zero, () => print('C'));  scheduleMicrotask(() => print('D'));  Future.value(1).then((_) => print('E'));  Future.sync(() => print('F'));  print('G');}

    A F G D E B C.

  13. 13

    What is a Completer, and when would you use one?

    A Completer<T> creates a Future that you complete by hand: you hand out completer.future, then call complete(value) or completeError(error, stackTrace) when the result arrives.

    Future<Position> currentPosition(LocationSdk sdk) {  final completer = Completer<Position>();   sdk.requestOnce(    onSuccess: (pos) {      if (!completer.isCompleted) completer.complete(pos);…
  14. 14

    What is FutureOr<T> and when would a function return it instead of Future<T>?

    FutureOr<T> is a type that means either a T or a Future<T>.

    FutureOr<User> getUser(String id) {  final cached = _cache[id];  if (cached != null) return cached;   // synchronous: no event-loop turn  return _fetchAndCache(id);           // Future<User>}…
  15. 15

    Why does catchError here never run when id is empty, and how do you fix it?

    Future<User> load(String id) {  if (id.isEmpty) throw ArgumentError.value(id, 'id');  return api.getUser(id);} void onTap(String id) {  load(id).catchError((Object e) {    log('failed: $e');    return User.guest();  });}

    load is not async, so the throw happens on the caller's stack before any Future exists.

  16. 16

    Find the bug: when the device is offline, loadProfile throws instead of returning the cached profile.

    Future<Profile> loadProfile(String id) async {  try {    return api.fetchProfile(id);  } on NetworkException {    return cache.read(id);  }}

    return api.fetchProfile(id) hands back the Future without awaiting it, so the try block is left before the request fails.

  17. 17

    How do you add a timeout and retries to a Future?

    future.timeout(duration) returns a new Future that fails with a TimeoutException if the original takes too long, or returns a fallback from onTimeout.

    Future<T> retry<T>(  Future<T> Function() task, {  int maxAttempts = 4,  Duration baseDelay = const Duration(milliseconds: 500),  bool Function(Object error) retryIf = _isTransient,}) async {…
  18. 18

    How do you catch all uncaught errors in a Flutter app, and why does wrapping runApp() in try/catch not work?

    Set two hooks in main(): FlutterError.onError for errors the framework catches in build, layout, paint and gestures, and PlatformDispatcher.instance.onError for uncaught async errors.

    void main() {  WidgetsFlutterBinding.ensureInitialized();   FlutterError.onError = (FlutterErrorDetails details) {    FlutterError.presentError(details);          // keep the console output    crashReporter.recordFlutterError(details);…
  19. 19

    Dart Futures cannot be cancelled. How do you stop work the user has already navigated away from?

    You cancel whatever is doing the work, not the Future: a Future is only a handle to a result.

    class _SearchState extends State<Search> {  int _latest = 0;  List<Hit> _hits = const [];  CancelableOperation<List<Hit>>? _op;   Future<void> onQueryChanged(String q) async {…
  20. 20

    You store a Future in a field and await it in a try/catch a second later, yet the app still logs an unhandled exception. Why?

    A Future reports its error as unhandled at the moment it completes if no listener is attached yet.

    class Warmup {  // wrong: starts now and may fail before anyone listens  final Future<Config> _config = loadConfig();   Future<void> run() async {    await Future<void>.delayed(const Duration(seconds: 1));…
  21. 21

    Crash reports show a stack trace that ends at the event loop instead of the failing line. What loses the origin of an async error?

    Any place that rethrows or wraps an error without its original StackTrace.

    // wrong: a new throw builds a new traceFuture<void> saveLosing(Order order) async {  try {    await repo.save(order);  } catch (e) {    throw SaveFailed('$e'); // the trace now starts on this line…
  22. 22

    Dart is single-threaded, so how can two async calls still race, and how do you serialize access to a shared resource?

    Every await is a gap where other code runs, so two calls that read, await, then write can interleave even on one thread.

    class TokenManager {  Future<Tokens>? _refreshing;   // every caller during a refresh shares one request  Future<Tokens> refresh() =>      _refreshing ??= _doRefresh().whenComplete(() => _refreshing = null);…