47

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

6 Answers 6

122

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.

Sign up to request clarification or add additional context in comments.

5 Comments

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/…
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.
It's later here. Let me know if you want further explanations and i'll try to elaborate more tomorrow.
Thanks! I had a Map instead of a list, so this answer helped as well:stackoverflow.com/questions/52276412/…
what about Future<void> functions...creating a simple list from forEach and passing that list to firebase...
44

Also possible

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

Comments

22

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

Comments

6

I encountered the similar issue. The problem is that dart will NOT wait for "forEach" contrary to public believe. There are two solutions:

1) Convert forEach to for loop as indicated by others. Another is use Future:
2) await Future.forEach(list, (item) async {
   // your code
    final result = await getMyResult();
   });

Comments

0

Another option

Future.wait(someList.map((item) => something_returns_future(item)));

Comments

0

Future.forEach() will execute futures sequentially. The iteration waits until the future is completed before continuing with the next element.

Future.wait() will execute futures in parallel. Returns a future which will complete once all the provided futures have completed.

See Future class

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.