2024/1/1 Hokkaido

Say Goodbye to Future.wait([]) in Dart

Andrew Chen
3 min readMay 3, 2024

As Dart developers, we’re no strangers to asynchronous programming and the power of Futures. In the past, when we needed to wait for multiple futures concurrently, we relied on the Future.wait([]) method, which returned a List<T>. However, this approach had a significant drawback: we had to manually cast the results to their desired types, which could lead to verbose and error-prone code.

final results = await Future.wait([
Future.value("andrew"),
Future.value(1984),
]);
expect((results[0] as String).toUpperCase(), "ANDREW");
expect((results[1] as int).isNegative, false);

Fortunately, there’s a more elegant solution that leverages the power of extensions and async/await syntax: the zipWith extension method.

Introducing zipWith

The zipWith extension method allows you to combine the results of two futures into a typed tuple, eliminating the need for manual casting. Here's how it works:

final (name, year) = await Future.value("andrew")
.zipWith(Future.value(1984));
expect(name.toUpperCase(), "ANDREW");
expect(year.isNegative, false);

In this example, we’re combining the results of two futures: one that resolves to the string “andrew” and another that resolves to the integer 1984. The zipWith method returns a tuple (T, T2) where T is the type of the first future, and T2 is the type of the second future.

But wait, there’s more! You can even chain multiple zipWith calls to combine the results of three or more futures:

final ((name, year), married) = await Future.value("andrew")
.zipWith(Future.value(1984))
.zipWith(Future.value(false));
// ...
expect(married, false);

In this example, we’re combining three futures, resulting in a nested tuple ((T, T2), T3).

Additional Points

  1. Handling Futures Without Type Safety: If you doesn’t care about type safety and is dealing with too many Futures that are difficult to zipWith, they can still use Future.wait([]) instead.
  2. Extending zipWith for More Futures: You can create a Future<(T1, T2)> (Future<T1>, Future<T2>).wait() method and similar ones for up to 9 futures (e.g., Future<(T1, T2, T3)> (Future<T1>, Future<T2>, Future<T3>).wait()). This is enough, because when the number of futures exceeds 9, you are likely not concerned about the type safety of the results.

Tuple(Future<T1>, Future<T2>).wait()

final (name, year) = await (
Future.value("andrew"),
Future.value(1984),
).wait();
expect(name.toUpperCase(), "ANDREW");
expect(year.isNegative, false);

final (name, year, married) = await
Future.value("andrew"),
Future.value(1984),
Future.value(false)
).wait();
// ...
expect(married, false);

Under the Hood

So, how does zipWith work its magic? Here's the implementation:

extension FutureZipX<T> on Future<T> {
Future<(T, T2)> zipWith<T2>(Future<T2> future2) async {
late T result1;
late T2 result2;
await Future.wait([
then((it) => result1 = it),
future2.then((it) => result2 = it)
]);
return (result1, result2);
}
}

extension TupleFuture2X<T1, T2> on (Future<T1>, Future<T2>) {
Future<(T1, T2)> wait() async {
final (a, b) = this;
return (await a, await b);
}
}

extension TupleFuture3X<T1, T2, T3> on (Future<T1>, Future<T2>, Future<T3>) {
Future<(T1, T2, T3)> wait() async {
final (a, b, c) = this;
return (await a, await b, await c);
}
}

Wrapping Up

By embracing the power of zipWith, you can write cleaner, more expressive code when working with multiple asynchronous operations. No more manual casting or fiddling with List<dynamic> – just concise, type-safe tuples that make your code easier to read and maintain.

So, the next time you find yourself juggling multiple futures, give zipWith a try and experience the joy of streamlined asynchronous programming in Dart.

See Also

DardPad:

If you do have many properties need to combine from futures for example with a bad restful APIs design:

Another way:

final a = Future.value("a");
final b = Future.value("b");
final c = Future.value("c");
final d = Future.value(4);
UserProfile(await a, await b, await c, await d);

Flatten nested tuples: https://gist.github.com/yongjhih/89a199ec14127f92a001d2e15e8a32ac

--

--