My async call is returning before list is populated in forEach loop

19,430

Solution 1

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 user = User.fromJson(json.decode(contents));
    String name = user.NameLast + ", " + user.NameFirst;
    print(name);
    l.add(name);
  }
  return l;
}

returns the list l and then processes the asyc forEach(...) callbacks

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 user = User.fromJson(json.decode(contents));
    String name = user.NameLast + ", " + user.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.

Solution 2

Also possible

await Future.forEach(yourList, (T elem) async { ...async staff });

Solution 3

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.

Broken example

Incorrectly assumes forEach will wait on futures:

Future<void> brokenExample(List<String> someInput) async {    
  List<String> results;

  someInput.forEach((input) async {
    String result = await doSomethingAsync(input);
    results.add(result);
  });

  return results;
}

Corrected example

Waits on the async functions to complete, using Future.wait and .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;
}
Share:
19,430
Jerry
Author by

Jerry

This is the stackExchange, if you really are curious, then ask... I am sure that someone has an answer...

Updated on June 25, 2022

Comments

  • Jerry
    Jerry almost 2 years

    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();
            });
          });
        }
      });