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');
}
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');
}
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 Futures
s, it's possible to return List
s 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);
}
This isn't very elegant because we have to wait so, so long in order for our integers to come back...
Deferred Execution and Stream
s
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));
}
}
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!
Top comments (0)