Reputation: 1583
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);
}
Upvotes: 59
Views: 33723
Reputation: 10234
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)];
Upvotes: 10
Reputation: 1161
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();
Upvotes: 17
Reputation: 4013
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.
Upvotes: 15
Reputation: 76223
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);
}
Upvotes: 13
Reputation: 13560
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.
Upvotes: 120