Flutter - Dart : wait a forEach ends

2020-03-06 08:09发布

问题:

I try to modify a string using data in each node I retrieve from Firebase database, and then to write a file with the modifed string (called "content").

Here is what I tried :

// Retrieve initial content from Firebase storage
var data = await FirebaseStorage.instance.ref().child("...").getData(1048576);
var content = new String.fromCharCodes(data);

// Edit content with each node from Firebase database
final response = await FirebaseDatabase.instance.reference().child('...').once();
response.value.forEach((jsonString) async {
     ...
     // cacheManager.getFile returns a Future<File>
     cacheManager.getFile(signedurl).then((file){ 
         // Modify content
         content=content.replaceAll('test',file.path);
     });
});

// Finally write the file with content
print("test");
final localfile = File('index.html');
await localfile.writeAsString(content);

Result :

"test" is shown before the forEach ends.

I found that we can do in Dart (https://groups.google.com/a/dartlang.org/forum/#!topic/misc/GHm2cKUxUDU) :

await Future.forEach

but in my case if I do : await Future.response.value.forEach (sounds a bit weird)

then I get :

Getter not found: 'response'. await Future.response.value.forEach((jsonString) async {

How to wait that forEach ends (with "content" modified) before to write the file with new content?

Any idea?

回答1:

If you use for(... in ) instead of forEach you can use async/await

Future someMethod() async {
  ...

  for(final jsonString in response.value) {
     ...
     // cacheManager.getFile returns a Future<File>
     cacheManager.getFile(signedurl).then((file){ 
         // Modify content
         content=content.replaceAll('test',file.path);
     });
  });
}

With forEach the calls fire away for each jsonString immediately and inside it await works, but forEach has return type void and that can't be awaited, only a Future can.



回答2:

You defined the callback for forEach as async, which means that it runs asynchronously. In other words: the code inside of that callback runs independently of the code outside of the callback. That is exactly why print("test"); runs before the code inside of the callback.

The simplest solution is to move all code that needs information from within the callback into the callback. But there might also be a way to await all of the asynchronous callbacks, similar to how you already await the once call above it.

Update I got working what I think you want to do. With this JSON:

{
  "data" : {
    "key1" : {
      "created" : "20181221T072221",
      "name" : "I am key1"
    },
    "key2" : {
      "created" : "20181221T072258",
      "name" : "I am key 2"
    },
    "key3" : {
      "created" : "20181221T072310",
      "name" : "I am key 3"
    }
  },
  "index" : {
    "key1" : true,
    "key3" : true
  }
}

I can read the index, and then join the data with:

final ref = FirebaseDatabase.instance.reference().child("/53886373");
final index = await ref.child("index").once();
List<Future<DataSnapshot>> futures = [];
index.value.entries.forEach((json) async {
  print(json);
  futures.add(ref.child("data").child(json.key).once());
});
Future.wait(futures).then((List<DataSnapshot> responses) {
  responses.forEach((snapshot) {
    print(snapshot.value["name"]);
  });
});


回答3:

Have you tried:

File file = await cacheManager.getFile(signedurl);
content = content.replaceAll('test', file.path);

instead of:

cacheManager.getFile(signedurl).then((file){ ...});

EDIT:

Here's a fuller example trying to replicate what you have. I use a for loop instead of the forEach() method:

void main () async {
  List<String> str = await getFuture();
  print(str);
  var foo;

  for (var s in str) {
    var b = await Future(() => s);
    foo = b;
  }

  print('finish $foo');
}

getFuture() => Future(() => ['1', '2']);