DEV Community

Jake Varness
Jake Varness

Posted on

Deferred Execution and Dart's Concurrency Model

You gotta hand it to Google: they make languages with great asynchronousy models. Go (another Google language) is infamous for it's simple concurrency model that makes it really easy to make your code asynchronous.

However, Go isn't the only Google language that wants to make your life in a concurrent world easier. Dart comes with it's own paradigms for making concurrency extremely easy.

Keywords

This is going to be a great reference for this article. If you haven't read this I would highly recommend it.

Asynchronous functions in Dart will have either async or async* in the signature of the function. This means that the function will return either a Future for async, or a Stream for async*. There are subtle differences between the two, but the premises are the same for both: you can execute the function, and continue execution without waiting for the execution of the function to finish.

Future

When writing code in Dart, you should use Future when you want to are waiting for an object to come back, and ONLY one object to come back. That being said, it's still possible to return a Future<List<int>> from your API, but when it makes sense to, you might want to consider using a Stream<int> instead. I'll explain what the differences are in a second.

When you're consuming an API that returns a Future, there are a couple of different ways you can handle it:

import 'dart:async';

Future<Null> main() async {
  Future<String> name = futureName();
  print('Waiting for the name to come back...');
  name.then((theName) => print('Hello $theName!'));
}

Future<String> futureName() async {
  return new Future.delayed(new Duration(seconds: 3), () => 'Jake');
}
Enter fullscreen mode Exit fullscreen mode

If you have ever used a promise in JavaScript, you might notice that a Future acts in a very similar way to a promise. There are a variety of different ways you can handle a Future depending on what your needs are.

This is an okay way of handling the Future returned from futureName(), but I prefer using the await syntax:

import 'dart:async';

Future<Null> main() async {
  print('Waiting for the name to come back...');
  String name = await futureName();  
  print('Hello $name!');
}

Future<String> futureName() async {
  return new Future.delayed(new Duration(seconds: 3), () => 'Jake');
}
Enter fullscreen mode Exit fullscreen mode

In this example, we're using the await keyword, which essentially tells Dart to wait for the Future to close and then return what the Future is supposed to return. The code is still asynchronous, but the code will wait before execution. Try moving the first print below the await and notice that it takes a few seconds before executing that statement.

With Futuress, it's possible to return Lists as well:

import 'dart:async';

Future<Null> main() async {
  print('Just waiting for my list...');
  List<int> integers = await deferredExecution();
    for (var x in integers) {
    print(x);
  }
}

Future<List<int>> deferredExecution() async {
  List<int> list = [];
  int totalWait = 0;
    for (var x = 5; x >= 0; x--) {
    // sleep for x seconds...
    list.add(x);
    totalWait += x;
  }
  return new Future.delayed(new Duration(seconds: totalWait), () => list);
}
Enter fullscreen mode Exit fullscreen mode

This isn't very elegant because we have to wait so, so long in order for our integers to come back...

Deferred Execution and Streams

If you're returning a bunch of things that take a long time to come back, try returning a Stream and using deferred execution instead.

Deferred execution essentially means that you're returning back something that you can iterate over, but the items will come back asynchronously. If you have ever written C# code and you've used deferred execution before, Dart's yield keyword acts exactly the same as C#'s yield, but where C# requires that methods that use yield return Enumerable<T>, Dart enforces that a Stream is returned.

So in the deferredExecution() function, we can change the code to use the yield keyword to return our integers more efficiently:

import 'dart:async';

Future<Null> main() async {
  Stream<int> integers = deferredExecution();
  print('Just waiting for my stream...');
  await for (var x in integers) {
    print(x);
  }
}

Stream<int> deferredExecution() async* {
  for (var x = 5; x >= 0; x--) {
    yield x;
    // sleep for x seconds...
    await new Future.delayed(new Duration(seconds: x));
  }
}
Enter fullscreen mode Exit fullscreen mode

In this example, we're using an await for loop to iterate over the things that were retrieved from the Stream. This special syntax will return items back into the loop as they are retrieved until the Stream closes.

In this case, we still wait the same amount of time to get all of the information back, but this is more efficient because we are able to act on what's returned from the function as each element is returned thanks to deferred execution.

I hope that this made sense. I know that some of these examples weren't the best, but I hope that this was educational!

Latest comments (0)