Search code examples
dartdart-async

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?


Solution

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

    .then((RedisClient rc) => (redisClient = rc).exists(indexKey))