Fundamentals 19 min read

Understanding Dart Future: Asynchronous Tasks, Error Handling, and async/await

This article explains the Dart Future object, its role in representing asynchronous task results, how to listen to success and error callbacks with then, catchError, and whenComplete, the special FutureOr type, chaining with then, and simplifying code using async/await, illustrated with Flutter examples.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Understanding Dart Future: Asynchronous Tasks, Error Handling, and async/await

In Dart, the Future class encapsulates the result of an asynchronous operation, similar to Promise in JavaScript or Future in C++/Java. When a task is dispatched it is in an uncompleted state and the returned Future represents the expected outcome.

For example, File.writeAsString returns a Future . The generic type of Future indicates the type of the eventual result:

void saveToFile(TaskResult result) {
  String filePath = path.join(Directory.current.path, "out.json");
  File file = File(filePath);
  String content = json.encode(result);
  Future
futureFile = file.writeAsString(content);
}

The Future can be observed using two primary methods:

then – called when the task completes successfully.

catchError – called when the task ends with an exception.

Both methods return a new Future , allowing further chaining. A typical success listener looks like:

futureFile.then((File file) {
  // tag1
  print('写入成功:${file.path}');
});

And an error listener can capture the exception and its stack trace:

futureFile.catchError((err, stack) {
  print("catchError::[${err.runtimeType}]::${err}");
  print("stack at ::[${stack.runtimeType}]::${stack}");
});

If both success and error handling are needed, whenComplete provides a finally‑like callback that runs regardless of the outcome:

futureFile.whenComplete(() {
  print("=======Complete=======");
});

The FutureOr type represents either a concrete value ( T ) or a Future . It is used as the return type of onError in catchError and of the callback passed to whenComplete . For example, an error handler can return a fallback File object:

futureFile.catchError((err) {
  print("catchError::[${err.runtimeType}]::${err}");
  return File('this is error file');
});

The then method itself is generic: Future then (FutureOr onValue(T value), {Function? onError}) . By returning another Future (e.g., file.readAsString() ) from the callback, you can chain asynchronous operations:

Future
thenResult = futureFile.then
((File file) {
  print('写入成功:${file.path}');
  return file.readAsString();
});
thenResult.then((String value) {
  print('读取成功:${value}');
});

Using async / await removes the nesting of callbacks and makes the flow look synchronous. The same file‑write example becomes:

void readFile(TaskResult result) async {
  String filePath = path.join(Directory.current.path, "config", "config.json");
  File configFile = File(filePath);
  String configContent = await configFile.readAsString(); // tag1
  String assetsPath = json.decode(configContent)['assets_file_path'];
  File assetsFile = File(assetsPath);
  String assetsContent = await assetsFile.readAsString();
  print(assetsContent);
  dynamic map = json.decode(assetsContent);
  int count = map['read_count'];
  map['read_count'] = count + 1;
  File resultFile = await assetsFile.writeAsString(json.encode(map));
  print('写入成功:${resultFile.path}');
}

In a Flutter UI, a Future.delayed can simulate a loading task while keeping the UI responsive:

int _counter = 0;
TaskState _state = TaskState.initial;

void _doIncrementTask() async {
  renderLoading();
  await Future.delayed(const Duration(seconds: 2));
  _counter++;
  renderLoaded();
}

void renderLoading() {
  setState(() { _state = TaskState.loading; });
}

void renderLoaded() {
  setState(() { _state = TaskState.initial; });
}

Contrast this with a synchronous sleep call, which blocks the UI thread and causes the app to freeze.

Finally, the article notes that Future.delayed also accepts a second argument – a computation function returning FutureOr – allowing you to produce a result or throw an exception after the delay. This enables patterns such as:

void _doIncrementTask() async {
  renderLoading();
  try {
    _counter = await Future.delayed(const Duration(seconds: 1), computation);
    renderLoaded();
  } catch (e) {
    renderError();
  }
}

FutureOr
computation() {
  int counter = _counter + 1;
  if (counter % 3 == 0) throw 'error';
  return counter;
}

void renderError() {
  setState(() { _state = TaskState.error; });
}

The article concludes that mastering Future , its callbacks, and async/await covers most asynchronous scenarios in Dart and Flutter, while more advanced stream handling will be explored in a future article.

DartFlutterAsynchronous ProgrammingError handlingasyncFutureawait
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.