Dart allows for chaining futures to invoke more than one async method in sequence without nesting callbacks, which is awesome.
Let's say we want to first connect to a data store like Redis, and then run a bunch of sequential reads:
Future<String> FirstValue(String indexKey)
{
return RedisClient.connect(Config.connectionStringRedis)
.then((RedisClient redisClient) => redisClient.exists(indexKey))
.then((bool exists) => !exists ? null : redisClient.smembers(indexKey))
.then((Set<String> keys) => redisClient.get(keys.first))
.then((String value) => "result: $value");
}
Four async methods and yet the code is fairly easy to read and understand. It almost looks like the steps are executed synchronously and in sequence. Beautiful! (Imagine having to write the same code using nested JavaScript callbacks...)
Unfortunately, this won't quite work: the RedisClient we get from the .connect
method is only assigned to a local variable which is not in scope for the subsequent .then
s. So, redisClient.smembers
and redisClient.get
will actually throw a null pointer exception.
The obvious fix is to save the return value in another variable with function scope:
Future<String> FirstValue(String indexKey)
{
RedisClient redisClient = null;
return RedisClient.connect(Config.connectionStringRedis)
.then((RedisClient theRedisClient)
{
redisClient = theRedisClient;
return redisClient.exists(indexKey);
})
.then((bool exists) => !exists ? null : redisClient.smembers(indexKey))
.then((Set<String> keys) => redisClient.get(keys.first))
.then((String value) => "result: $value");
}
Unfortunately, this makes the code more verbose and less beautiful: there's now an additional helper variable (theRedisClient), and we had to replace one of the Lambda expressions with an anonymous function, adding a pair of curly braces and a return
statement and another semicolon.
Since this appears to be a common pattern, is there a more elegant way of doing this? Any way to access those earlier intermediate further down the chain?
Sometimes you need to have results of multiple async functions before starting to work on something else. To prevent multiple awaits, chaining futures in . then(), you can simply use Future. wait([]) that returns an array of results you were waiting for.
The difference between both is that async* will always return a Stream and offer some syntax sugar to emit a value through the yield keyword. async gives you a Future and async* gives you a Stream. When users marks a function as async or async* allows it to use async/await keyword to use a Future.
A future (lower case “f”) is an instance of the Future (capitalized “F”) class. A future represents the result of an asynchronous operation, and can have two states: uncompleted or completed. Note: Uncompleted is a Dart term referring to the state of a future before it has produced a value.
Summary of asynchronous programming in Dart Asynchronous function is a function that returns the type of Future. We put await in front of an asynchronous function to make the subsequence lines waiting for that future's result. We put async before the function body to mark that the function support await .
You can use a nested assignment to avoid curly braces and return
:
.then((RedisClient rc) => (redisClient = rc).exists(indexKey))
You can do scopes with futures too, by not putting all the 'then' calls at the same level. I'd do something like:
Future<String> FirstValue(String indexKey) =>
RedisClient.connect(Config.connectionStringRedis)
.then((RedisClient redisClient) =>
redisClient.exists(indexKey)
.then((bool exists) => !exists ? null : redisClient.smembers(indexKey))
.then((Set<String> keys) => redisClient.get(keys.first))
.then((String value) => "result: $value");
);
Indentation is always difficult with code like this. This example follows the Dart style guide, but I think it could be more readable with less indentation of the then
calls:
Future<String> FirstValue(String indexKey) =>
RedisClient.connect(Config.connectionStringRedis)
.then((RedisClient redisClient) =>
redisClient.exists(indexKey)
.then((bool exists) => !exists ? null : redisClient.smembers(indexKey))
.then((Set<String> keys) => redisClient.get(keys.first))
.then((String value) => "result: $value");
);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With