In the realm of programming, there exists a powerful utility known as . This often-overlooked gem enables developers to orchestrate concurrent execution of multiple asynchronous tasks, leading to improved performance and efficiency. Dart Future.wait() In this blog post, we will explore the problem domain where can save the day and delve into best practices for utilizing it effectively. Future.wait() The Problem Imagine you are developing a Dart application that needs to fetch data from multiple remote APIs simultaneously? That was our case and we got PR for this case scenario in a Dart/Flutter code I am managing, as it is listed in the code snippet below! Given I can’t share the whole code, I will embody this code smell with the power of later in this blog: Future.wait() void fetchFromRemoteApis() { final apiAFuture = fetchDataFromApi('API A'); final apiBFuture = fetchDataFromApi('API B'); final apiCFuture = fetchDataFromApi('API C'); apiAFuture.then((resultA) { print('Received result from API A: $resultA'); apiBFuture.then((resultB) { print('Received result from API B: $resultB'); apiCFuture.then((resultC) { print('Received result from API C: $resultC'); print('All API calls completed successfully!'); }).catchError((errorC) { print('An error occurred while fetching data from API C: $errorC'); }); }).catchError((errorB) { print('An error occurred while fetching data from API B: $errorB'); }); }).catchError((errorA) { print('An error occurred while fetching data from API A: $errorA'); }); } In this suboptimal approach, the code chained the callbacks sequentially for each API call. This results in nested callbacks, commonly known as the "callback hell" or "pyramid of doom" pattern. It leads to less readable and more error-prone code, especially when handling errors. then The code also first fetches data from API A. Upon successful completion, it proceeds to fetch data from API B. Finally, when API B completes successfully, it proceeds to fetch data from API C. At each stage, error handling is implemented using to capture and handle any errors that may occur during the API calls. catchError() The main disadvantage of this approach is that it lacks the efficiency of concurrent execution, as each API call waits for the previous one to complete before initiating the next. This can lead to longer overall execution time, especially when dealing with multiple independent asynchronous tasks. Traditionally, when dealing with asynchronous tasks, you might resort to executing them one after another, waiting for each one to complete them before starting the next. This sequential approach can lead to suboptimal performance, especially when network latencies come into play. , is a function that enables concurrent execution of asynchronous tasks. With this mighty tool at our disposal, we can dramatically enhance the efficiency of our code by fetching data from multiple remote APIs concurrently, taking advantage of parallelism and reducing overall execution time. Harnessing the Power of Future.wait() : Future.wait() : Here's how Future.wait() works We create a list of objects, each representing an asynchronous task, such as fetching data from a remote API. Future We pass this list of objects to . Future Future.wait() returns a new that completes when all the provided objects have completed. Future.wait() Future Future Saving the Day with Future.wait() Consider a scenario where your application needs to fetch data from three different remote APIs: A, B, and C. Without , you might fetch data from A, wait for it to complete, then fetch data from B, and finally fetch data from C. This sequential approach can be time-consuming and inefficient. Future.wait() However, by utilizing , you can kick off all three asynchronous tasks concurrently, reducing the overall execution time. The returned by will complete only when all three API calls have finished, allowing you to collect the results efficiently. Future.wait() Future Future.wait() So, I reviewed the code smell to make it better. Let’s proceed to that. : Writing Better Code To make the most of , here are a few best practices: Future.wait() Use when the tasks are truly independent and can be executed concurrently. If there are dependencies between tasks, consider using or to handle them. Future.wait() Future.then() async/await Ensure that the tasks passed to are truly asynchronous and return a . If not, consider wrapping synchronous code in a using or . Future.wait() Future Future Future.value() Future.microtask() Utilize error-handling mechanisms like or callbacks to handle exceptions that might occur during the execution of concurrent tasks. try-catch onError If there's a need to set a timeout for the completion of , you can use to handle situations where certain tasks take an unusually long time. Future.wait() Future.wait(...).timeout(...) The Fix: So here, I embodied the code smell and used to make it better. I have also chosen open random endpoints for you to see how effective it can serve concurrent execution: Future.wait() API import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; void main() { fetchFromRemoteApis(); } void fetchFromRemoteApis() async { final apiAFuture = fetchDataFromApi('https://jsonplaceholder.typicode.com/posts'); final apiBFuture = fetchDataFromApi('https://jsonplaceholder.typicode.com/comments'); final apiCFuture = fetchDataFromApi('https://jsonplaceholder.typicode.com/todos'); try { List<dynamic> results = await Future.wait<dynamic>([apiAFuture, apiBFuture, apiCFuture]); for (var result in results) { print('Received result: $result'); } print('All API calls completed successfully!'); } catch (error) { print('An error occurred: $error'); } } Future<dynamic> fetchDataFromApi(String apiUrl) async { final response = await http.get(Uri.parse(apiUrl)); // Check the response status if (response.statusCode == 200) { final jsonResponse = jsonDecode(response.body); return jsonResponse; } else { throw Exception('API request failed with status ${response.statusCode}'); } } As much as different software engineers can have differing opinions such as using with inside , which I think is unnecessary nested callbacks, or use only with which creates an additional callback function and introduces an extra layer of complexity, I believe the algorithmic complexity of the code smell is reduced drastically by the fix provided in this blog. .map() .then() Future.wait() .then() Future.wait() Conclusion In the ever-evolving world of Dart programming, it's essential to leverage the right tools for improved performance and efficiency. is a valuable utility that empowers developers to achieve concurrent execution of asynchronous tasks, effectively reducing execution time and optimizing resource utilization. By incorporating into your code, you can unlock the magic of concurrent programming and embrace the power of Dart to its fullest potential. Future.wait() Future.wait() So, the next time you find yourself juggling multiple asynchronous tasks, remember the spellbinding capabilities of and watch as your code performs its own brand of magic. Future.wait() If you need more resources on , kindly refer to and . Future.wait() https://api.dart.dev/stable/2.1.0/dart-async/Future/wait.html https://api.flutter.dev/flutter/dart-async/Future/wait.html Happy coding!