Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chaining Dart futures - possible to access intermediate results?

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 .thens. 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?

like image 719
Max Avatar asked Feb 27 '14 22:02

Max


People also ask

How do you wait for Future to complete Dart?

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.

What is the difference between Future and async and await in flutter?

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.

What is Future function in Dart?

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.

What is async await in Dart?

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 .


2 Answers

You can use a nested assignment to avoid curly braces and return :

.then((RedisClient rc) => (redisClient = rc).exists(indexKey))
like image 101
Alexandre Ardhuin Avatar answered Oct 28 '22 01:10

Alexandre Ardhuin


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"); 
    );
like image 21
lrn Avatar answered Oct 28 '22 00:10

lrn