Dart – 22 – Future and Completer

Future and Completer in Dart

Futures and Completers are essential components of asynchronous programming in Dart. They provide a structured way to work with operations that may take time to complete, such as network requests, file I/O, or computations. In this discussion, we’ll explore the significance of Futures and Completers, understand how they work, and see practical examples of their use.

Understanding Futures

Future in Dart represents a potential value or error that will be available at some point in the future. It is a placeholder for a result that may not be immediately available, making it ideal for handling asynchronous operations. Dart Futures provide a way to work with values that will be produced at a later time.

Creating and Using Futures

You can create a Future in Dart using the Future class. To work with asynchronous tasks, you typically use functions that return Futures. Once a Future is created, you can register callbacks to execute when the result is available, or handle errors if the operation fails.

Here’s an example of using a Future to simulate a delayed operation:


Future fetchUserData() {
  return Future.delayed(Duration(seconds: 2), () {
    return 'User Data';
  });
}

fetchUserData().then((data) {
  print('Fetched: $data');
}).catchError((error) {
  print('Error: $error');
});
    
Understanding Completers

Completer is an object that allows you to produce and complete a Future manually. It provides a way to control when the Future is resolved with a value or an error. Completers are particularly useful in situations where you need to handle asynchronous operations that don’t naturally return a Future.

Creating and Using Completers

To create a Completer in Dart, you instantiate the Completer class. You can then use the Completer’s methods to set the result of the associated Future using complete or handle errors with completeError.

Here’s an example of using a Completer to manually complete a Future:


Future fetchData() {
  final completer = Completer();
  
  Future.delayed(Duration(seconds: 2), () {
    completer.complete('Fetched Data');
  });

  return completer.future;
}

fetchData().then((data) {
  print('Fetched: $data');
}).catchError((error) {
  print('Error: $error');
});
    
Chaining and Combining Futures

Futures in Dart can be chained and combined to create more complex asynchronous workflows. You can use functions like then to execute code when a Future is completed successfully, and catchError to handle errors.

Here’s an example of chaining multiple Futures to perform sequential asynchronous operations:


Future fetchNumber() {
  return Future.delayed(Duration(seconds: 2), () {
    return 42;
  });
}

Future processNumber(int number) {
  return Future.delayed(Duration(seconds: 1), () {
    return 'Processed $number';
  });
}

fetchNumber()
  .then((number) => processNumber(number))
  .then((result) {
    print('Result: $result');
  })
  .catchError((error) {
    print('Error: $error');
  });
    
Asynchronous Awaits

Dart provides the async and await keywords to simplify asynchronous code. You can mark a function as async and use await to pause execution until a Future is completed. This makes asynchronous code more readable and synchronous in appearance.

Here’s an example of using async and await with Futures:


Future fetchAndProcessData() async {
  try {
    final data = await fetchUserData();
    final result = await processUserData(data);
    print('Result: $result');
  } catch (error) {
    print('Error: $error');
  }
}

fetchAndProcessData();
    
Conclusion

Futures and Completers are fundamental for handling asynchronous operations in Dart. They provide a structured and efficient way to manage delayed or time-consuming tasks, whether you’re fetching data from a server, performing computations, or working with any other asynchronous operation. Asynchronous programming with Dart is essential for building responsive and scalable applications, and Futures and Completers are key tools in your arsenal.