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 {...}





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






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.

Comments

Popular posts from this blog

paramiko-expect timeout is happening after executing the command

Export result set on Dbeaver to CSV

Opening a url is failing in Swift