57

Can I map some Iterable using async mapping function? Maybe it is a bug, that this code prints list of _Future imidiately, not ints after 1 or 5 seconds?

import 'dart:async';

Future<int> foo(int i) {
  var c = new Completer();
  new Timer(new Duration(seconds: 1), () => c.complete(i));
  return c.future;
}

main() {
  var list = [1,2,3,4,5];
  var mappedList = list.map((i) async => await foo(i));
  print(mappedList);
}
1
  • This won't work for inferred list, this just had to work because the list is the same as List<dynamic>, once the var is inferred with the same type as the RHS result, it fails. Commented May 13, 2023 at 11:43

5 Answers 5

116

The expression (i) async => await foo(i) still returns a future. You can use Future.wait(mappedList) to wait till all created futures are completed.

2
  • 39
    So it should be List mappedList = await Future.wait(list.map((i) async => await foo(i)));.
    – Nae
    Commented May 1, 2020 at 18:46
  • 1
    This also works: List mappedList = await Future.wait(list.map((i) => foo(i)));, because it is able to detect the returned mapped value as a Future without the need to wait. Commented May 13, 2023 at 11:46
16

other answers didn't really work in my case, ended up using rxdart's asyncMap like this :

Observable.fromIterable(list)
      .asyncMap((item) => foo(item))
      .toList();

Edit: Observable class has been discontinued since rxdart 0.23.0, you can use Streams instead like this:

Stream
 .fromIterable(list)
 .asyncMap((item) => foo(item))
 .toList();
15

Your misunderstanding is that async functions return a Future, not a value. await does not convert async to sync.

var mappedList = list.map(
  (i) async => await foo(i) // Returns a Future, not an int
);

You are printing are the Futures returned by (i) async => await foo(i).

Those Futures complete when the chain of Futures within them complete. When the Timer fires: foo() completes, then await foo(i), then your mapping function.

Compare with:

main() async {
  List<int> list = [1,2,3,4,5];
  Iterable<Future<int>> mapped;

  // Prints ints 1 second apart
  mapped = list.map((i) => foo(i));
  for(Future<int> f in mapped) {
    print(await f);
  }

  // Prints ints all at once, after 1 second wait
  mapped = list.map((i) => foo(i));
  for(Future<int> f in mapped) {
    f.then(print);
  }
}

On Dartpad: https://dartpad.dartlang.org/151949be67c0cdc0c54742113c98b291

Some things to note:

List.map() returns a lazy Iterable (not a List) which means the mapping function isn't called until the Iterable is iterated through.

The first loop waits for each Future to complete before printing and moving on to the next item in the Iterable, the mapping function for the next item (and hence foo()) is called after printing each value, so values are printed at 1 second intervals.

The second loop iterates through the Iterable immediately, setting up a print function to execute after each Future completes. 5 instances of function foo() are called at once, which all return approximately 1 second later, then all 5 values are printed.

13

Adding some type will explain what's going on:

main() async {
  var list = [1,2,3,4,5];
  Iterable<Future<int>> mappedList = list.map((i) async => await foo(i));
  print(mappedList); // you print an Iterable of Future 

  // to get the list of int you have to do the following
  Future<List<int>> futureList = Future.wait(mappedList);
  List<int> result = await futureList;
  print(result);
}
1
  • Doesn't work for me. I get Exception at result = await futureList. Exception has occurred. _AssertionError (Failed assertion: boolean expression must not be null)
    – Kent
    Commented Aug 31, 2019 at 2:23
10

With today's Dart, there's no problem. Forget map, just build your list directly:

final list = [for (final item in items) await buildAsync(item)];
2
  • it seems not working in parallel
    – Yin
    Commented Apr 2, 2022 at 7:31
  • @Yin This is a simple sequential for-loop, so it will wait on each iteration. To maximize parallelization of I/O bound tasks, you should first create a list of futures, then await those futures. E.g., final futures = items.map((item) => buildAsync(item)).toList(); final list = [for (final future in futures) await future]. The last line can also be written final list = Future.wait(futures). Commented Oct 24, 2022 at 1:16

Not the answer you're looking for? Browse other questions tagged or ask your own question.