My async call is returning before list is populated in forEach loop
My async call is returning before list is populated in forEach loop
I have a routine which gets a list of filenames from the device, then reads the file(s) to build a list. However, the calling routine always returns with zero items. I print the filenames, so I know they exist, however, it appears that the async is returning before I read the files. I used similar code when making an HTTP call. But, something here is causing the routine to return the list even though it hasn't completed. Perhaps, it is possible that I am calling it at the wrong time? I am calling retrieveItems here:
@override
void initState() {
super.initState();
retrieveItems();
}
Eventually I will have a refresh button, but for now I'd simply like the list to populate with the data from the files...
Callee
Future<List<String>> readHeaderData() async {
List<String> l = new List();
List<String> files = await readHeaders(); // Gets filenames
files.forEach((filename) async {
final file = await File(filename);
String contents = await file.readAsString();
User usr = User.fromJson(json.decode(contents));
String name = usr.NameLast + ", " + usr.NameFirst;
print(name);
l.add(name);
}
return l;
Caller
void retrieveItems() async {
LocalStorage storage = new LocalStorage();
await storage.readHeaderData().then((item) {
try {
if ((item != null ) &&(item.length >= 1)) {
setState(() {
users.clear();
_users.addAll(item);
});
} else {
setState(() {
_users.clear();
final snackbar = new SnackBar(
content: new Text('No users found.'),
);
scaffoldKey.currentState.showSnackBar(snackbar);
});
}
} on FileNotFoundException catch (e) {
print(e.toString()); //For debug only
setState(() {
_users.clear();
});
});
}
});
2 Answers
2
This code
Future<List<String>> readHeaderData() async {
List<String> l = new List();
List<String> files = await readHeaders(); // Gets filenames
files.forEach((filename) async {
final file = await File(filename);
String contents = await file.readAsString();
User usr = User.fromJson(json.decode(contents));
String name = usr.NameLast + ", " + usr.NameFirst;
print(name);
l.add(name);
}
return l;
}
returns the list l
and then processes the asyc forEach(...)
callbacks
l
forEach(...)
If you change it to
Future<List<String>> readHeaderData() async {
List<String> l = new List();
List<String> files = await readHeaders(); // Gets filenames
for(var filename in files { /// <<<<==== changed line
final file = await File(filename);
String contents = await file.readAsString();
User usr = User.fromJson(json.decode(contents));
String name = usr.NameLast + ", " + usr.NameFirst;
print(name);
l.add(name);
}
return l;
}
the function will not return before all filenames are processed.
files.forEach((filename) async {
means that you can use await
inside the callback, but forEach
doesn't care about what (filename) async {...}
returns.
await
forEach
(filename) async {...}
There is a list
files
with a method forEach(f)
that internally calls the function f
you pass to it for every element in files
. forEach()
doesn't return anything. If you use instead list.map(f)
then you will get one Future
returned from f
for each item in files
. You can then use await Future.all(files.map(f))
which will await all Futures and only then continue with the code that follows below. The loop for(...) { ... }
is not just a method that calls functions passed to it, it is a language construct where statements and expressions are executed sequentially.– Günter Zöchbauer
Jun 29 at 20:04
files
forEach(f)
f
files
forEach()
list.map(f)
Future
f
files
await Future.all(files.map(f))
for(...) { ... }
It's later here. Let me know if you want further explanations and i'll try to elaborate more tomorrow.
– Günter Zöchbauer
Jun 29 at 20:04
To expand on Günter's comment regarding using list.map(f)
, here's an example of converting a forEach
call so that it works correctly.
list.map(f)
forEach
Incorrectly assumes forEach
will wait on futures:
forEach
Future<void> brokenExample(List<String> someInput) async {
List<String> results;
someInput.forEach((input) async {
String result = await doSomethingAsync(input);
results.add(result);
});
return results;
}
Waits on the async functions to complete, using Future.wait
and .map()
:
Future.wait
.map()
Future<void> correctedExample(List<String> someInput) async {
List<String> results;
await Future.wait(someInput.map((input) async {
String result = await doSomethingAsync(input);
results.add(result);
}));
return results;
}
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
That worked. I appreciate the quick response. May I ask where in the docs I could find that the difference between for and foreach, since it doesn't seem to be clearly defined when it comes to async calls. I read through webdev.dartlang.org/articles/performance/event-loop and I thought that I clearly understood futures, however, there appears to be times that the "await" makes sense, and times it doesn't. Though, now I will make sure to use for in this scenario. This shows it, but not why... stackoverflow.com/questions/24437673/…
– Jerry
Jun 29 at 18:29