Dart Future.wait for multiple futures and get back results of different types
Asked Answered
W

6

34

I'm using Flutter to download 3 different sets of data from a server, then do something with all 3 sets. I could do this:

List<Foo> foos = await downloader.getFoos();
List<Bar> bars = await downloader.getBars();
List<FooBars> foobars = await downloader.getFooBars();

processData(foos, bars, foobars);

But I'd prefer to download all 3 data sets asynchronously in parallel. I've seen that Dart has this Future.wait method:

Future<List<T>> wait <T>(
   Iterable<Future<T>> futures, {
   bool eagerError: false, 
   void cleanUp(
      T successValue
   )
}) 

However it looks like this will only return values of the same type (T). I have 3 different types, so I don't see how I can use this and get my 3 data sets back.

What's the best alternative way to achieve this?

Thanks!

Wolverine answered 8/4, 2020 at 16:26 Comment(1)
I've added an updated solution that will retain type safety with multiple parallel futures in both pre-dart 3 and post-dart 3: https://mcmap.net/q/435229/-dart-future-wait-for-multiple-futures-and-get-back-results-of-different-typesTilburg
E
57

In Dart 3, you should use a Record of Futures instead of a List/Iterable so that you can have heterogeneous types. Dart 3 provides wait extensions for such Records that are similar to Future.wait. (See sudormrfbin's answer for an example.)

If you must use Future.wait, you need to adapt each of your Future<T>s to a common type of Future. You could use Future<void> and assign the results instead of relying on return values:

late List<Foo> foos;
late List<Bar> bars;
late List<FooBars> foobars;

await Future.wait<void>([
  downloader.getFoos().then((result) => foos = result),
  downloader.getBars().then((result) => bars = result),
  downloader.getFooBars().then((result) => foobars = result),
]);

processData(foos, bars, foobars);

Or if you prefer await to .then(), the Future.wait call could be:

await Future.wait<void>([
  (() async => foos = await downloader.getFoos())(),
  (() async => bars = await downloader.getBars())(),
  (() async => foobars = await downloader.getFooBars())(),
]);
Extremist answered 8/4, 2020 at 17:31 Comment(0)
T
11

2024 Update: Dart 3 Solution (Records/Patterns)

With Dart 3, you no longer need a helper. You can use records (up to 9 futures).

// Calling this will start the function execution
Future<List<Foo>> foos = downloader.getFoos();
Future<List<Bar>> bars = downloader.getBars();

// Will run in parallel until both are done
final (foos, bars) = await (foosFunction, barsFunction).wait;

// Do stuff with the results since both are complete - and type safe!
print(foos[0]);
print(bars[0]);

Pre-Dart 3 Solution

I made a helper function that utilizes some of the logic in the other answers. It uses the tuple package, but you can write it yourself pretty easily (included below).

// Put this in future_utils.dart

/// Represents a 2-tuple, or pair.
class Tuple2<T1, T2> {
  /// Returns the first item of the tuple
  final T1 item1;

  /// Returns the second item of the tuple
  final T2 item2;

  /// Creates a new tuple value with the specified items.
  const Tuple2(this.item1, this.item2);
}

Future<Tuple2<T1, T2>> await2<T1, T2>(
    Future<T1> firstFuture,
    Future<T2> secondFuture) async {
  late T1 item1;
  late T2 item2;
  await Future.wait<void>([
    (() async => item1 = await firstFuture)(),
    (() async => item2 = await secondFuture)(),
  ]);
  return Tuple2(item1, item2);
}

Then call it:

Future<List<Foo>> foos = downloader.getFoos();
Future<List<Bar>> bars = downloader.getBars();

// Will run in parallel
Tuple2<List<Foo>, List<Bar>> results = await await2(foos, bars);
// Do stuff with the results since both are complete
print(results.item1[0]);
print(results.item2[0]);

Now if you want one for 3 arguments, 4, or more you can just copy and paste (await3, await4). This isn't too crazy of a pattern, I've used it for multiple lets in Kotlin and also that Tuple library I linked.

Tilburg answered 18/2, 2022 at 19:0 Comment(0)
H
3

If you're using Dart 3, you can take advantage of records to do this:

Future<List<Foo>> foosFuture = downloader.getFoos();
Future<List<Bar>> barsFuture = downloader.getBars();
Future<List<FooBars>> foobarsFuture = downloader.getFooBars();

final (foos, bars, foobars) = await (foosFuture, barsFuture, foobarsFuture).wait;

processData(foos, bars, foobars);

This works because of the wait property defined on 2-element records via the FutureRecord2 extension. They are defined till FutureRecord9, so you're safe as long as you have less than 10 futures to await on like this.

There's a section in the language tour that covers this better than the API docs.

Halonna answered 9/9, 2023 at 19:55 Comment(0)
W
2

I think is not possible to do in a super nice fashion. All you can do is something like this:

void main() async {
  List<List<dynamic>> result = await Future.wait<List<dynamic>>([
    getStringData(),
    getIntData(),
  ]);

  print(result[0]);
  print(result[1]);
}

Future<List<String>> getStringData() {
  return Future.value(["a", "b"]);
}

Future<List<int>> getIntData() {
  return Future.value([1, 2]);
}
Wesle answered 8/4, 2020 at 16:37 Comment(0)
C
2

I'd say the correct answer is to use the official async package and FutureGroup:

void main()  {
  Future<String> future1 = getData(2);
  Future<String> future2 = getData(4);
  Future<String> future3 = getData(6);
  FutureGroup futureGroup = FutureGroup();
  futureGroup.add(future1);
  futureGroup.add(future2);
  futureGroup.add(future3);
  futureGroup.close();
  futureGroup.future.then((value) => {print(value)});
}

Future<String> getData(int duration) async {
  await Future.delayed(Duration(seconds: duration)); //Mock delay
  return "This a test data";
}

Cicero answered 1/12, 2021 at 19:5 Comment(1)
How does this solve the problem? FutureGroup would have the same problems with heterogeneous Futures as Future.wait.Extremist
Y
0

Well, type T is a generic type

You know your types, so you can work with them.

If your are using a FutureBuilder, you can access the different results using this. (same order you put them in the Future.wait method)

snapshot.data[0] // maybe type List<Foo>
snapshot.data[1] // maybe type List<Bar>
snapshot.data[2] // maybe type String
Yeomanry answered 8/4, 2020 at 16:50 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.