Iterating asynchronous operations in dart(using forEach and for..in loop)

Iterating asynchronous operations in dart(using forEach and for..in loop)

Most hight-level programming languages ship with iteration statements and Dart is not an exception. For one to handle highly scalable and efficient business logic using dart, a detailed knowledge of asynchronous operations, futures, loops among others is a can’t-do-without.

In this article, We’ll focus specifically on the for..in and forEach loops as used in asynchronous operations.

Generally, both the for-in and forEach constructs are used to loop over iterables(List, Set, etc). Both have a return type of void. .i.e(a new value can not be returned from these methods), neither do they expose the current index of the iteration unlike the typical for..loop.

Enough of the general notes, let’s see how these constructs handle asynchronous operations in dart.

Take a closer look at the examples below:

List<Person> persons = [
  Person(
    name: "Emeruche U.K",  
    images:["ImageProfile", "My Image2"],
  ),
];


class Person{
   String name;
   List<String> images;

   Person({this.name, this.images});
}
void main() {
  Future uploadImages({images, name})async{
    return ["Image 1", "image 2"];
  }

  Future updateProfile(Person person) async{
    List<String> newImageUrls = [];
    person.images.forEach((image) async{
       if (image.runtimeType == String) {
        dynamic url = await uploadImages(images: [image], name: person.name);
        print("forEach URL: $url");
        newImageUrls.addAll(url);
      } else {
        newImageUrls.add(image);
      }
    });
    print("Profile Images f1: $newImageUrls");

    person..images = newImageUrls;
  }

  updateProfile(persons[0]);

}

From the above snippet, using the for..Each construct might seem like a natural choice when looping over a list using async/await. Typically, we expect the synchronous-reading magic of async/await to do what it says and actually await the promise. Often times this gives an unexpected output or even throw a ReferenceError if the result of the operation is being used somewhere else in the application (as seen in above code).

To fix this, we can either switch to the for..in construct or use the Future.forEach method. This should do the trick

 Future updateProfile2(Person person) async {
    List<String> newImageUrls = [];
    for (var image in person.images) {
      if (image.runtimeType == String) {
        dynamic url = await uploadImages(images: [image], name: person.name);
        print("for..in URL: $url");
        newImageUrls.addAll(url);
      } else {
        newImageUrls.add(image);
      }
    }
    print("Profile Images f2: ${newImageUrls}");

    person..images = newImageUrls;
  }

First, we changed the for..Each block to a for..in construct…why? Switching to a for..in block solved the problem because the for..each returns the empty result set first before processing the await function inside the forEach. It just applies the function on each element and calls next without awaiting for the asynchronous operation to return. But the for..in construct on the other hand, actually awaits the result of the execution of the function before calling next

Future updateProfile3(Person person) async {
    List<String> newImageUrls = [];

    await Future.forEach(person.images, (image)async{
       if (image.runtimeType == String) {
        dynamic url = await uploadImages(images: [image], name: person.name);
        print("Future.forEach URL: $url");
        newImageUrls.addAll(url);
      } else {
        newImageUrls.add(image);
      }
    });

    print("Profile Images f3: $newImageUrls");

    person..images = newImageUrls;
  }

The Future.forEach() waits for each Future to be completed before moving to the next element.

When you need to wait for multiple Futures to complete irrespective of their order, you can use Future.wait() method like this:

Future.wait(person.images.map(functionCall));

or

Future.wait([futureOne(), futureTwo(), …]).then((res) => functionToCallAfterResult(res));

Error handling in asynchronous code

Errors from your asynchronous code can be handled using the try..catch block in dart. Essentially, if an error is thrown within the try block, such can be received and handled with the catch block.