Things to consider when rewriting code to use the new async/await syntax provided by Future::AsyncAwait.

In part 2 we looked at how to rewrite code using Future::Utils::repeat, into using the neater async/await syntax provided by Future::AsyncAwait. Now we'll conclude by taking a look at how to handle conditionals and concurrency structures.


Conditionals

One particularly awkward code structure to write using simple Future is how to perform a future-returning action conditionally in a sequence of other steps. The usual workaround often involves testing if the condition holds and if not, waiting on a "dummy" immediate future to cover the case that the conditional code isn't being invoked. Because await can be placed anywhere inside an async sub, including inside a regular if block, these awkward structures can be avoided and much simpler code written instead.

# previously
sub example1 {
  return ( $COND ? MAYBE_DO_THIS() : Future->done )->then(sub {
    return DO_SECOND();
  });
}

sub example2 {
  return DO_FIRST()->then(sub {
    return Future->done unless $COND;
    OTHER_STEPS();
    return MAYBE_DO_THIS();
  })->then(sub {
    DO_SECOND();
  });
}

# becomes
async sub example1 {
  await MAYBE_DO_THIS() if $COND;
  return await DO_SECOND();
}

async sub example2 {
  await DO_FIRST();
  if ($COND) {
    OTHER_STEPS();
    await MAYBE_DO_THIS();
  }
  return await DO_SECOND();
}

The await keyword can also be used inside the condition test for an if block:

# previously
sub example {
  my ($uid) = @_;
  
  return GET_USER_INFO($uid)->then(sub {
    my ($info) = @_;
    return Future->done if !$info;
    return PROVISION_USER($info);
  });
}

# becomes
async sub example {
  my ($uid) = @_;
  
  if(my $info = await GET_USER_INFO($uid)) {
    await PROVISION_USER($info);
  }
}

Convergent Flow

Often in future-based code there is some concurrency of multiple operations converging in one place by using Future->needs_all or related functions. Since this constructor returns a future we can use await on it much like any other. Since the results are returned in a same-ordered list as the futures it is waiting on, it is simple enough to unpack these in a list assignment:

# previously
sub example {
  return Future->needs_all(
    GET_X(),
    GET_Y(),
  )->then(sub {
    my ($x, $y) = @_;
    ...
  });
}

# becomes
async sub example {
  my ($x, $y) = await Future->needs_all(
    GET_X(), GET_Y(),
  );
  ...
}

Similarly, uses of Future->wait_any as for example used to implement a timeout, can use await on that:

# previously
sub example {
  return Future->wait_any(
    $loop->timeout_future(after => 10),
    GET_THING(),
  )->then(sub {
    my ($thing) = @_;
    ...
  });
}

# becomes
async sub example {
  my ($thing) = await Future->wait_any(
    $loop->timeout_future(after => 10),
    GET_THING(),
  );
  ...
}

Sometimes the "main" path of a call to Future->wait_any is not just one function call, but composed of multiple operations perhaps in a sequence of ->then chains, or a repeat loop. In this case it is a little more difficult to rewrite that path into async/await syntax because an await expression would cause the entire containing function to pause, and in any case does not yield a Future value suitable to pass into the ->wait_any.

In this case, we can use an inner async sub to contain the main path of code with await expressions, and immediately invoke it.

# previously
use Future::Utils 'repeat';
sub example {
  return Future->wait_any(
    $loop->delay_future(after => 10)
      ->then_fail("Failed to get item in 10 seconds"),
      
    (repeat {
      return GET_ITEM()
    } until => sub { $_[0]->get->is_success }),
  );
}

# becomes
async sub example {
  await Future->wait_any(
    $loop->delay_future(after => 10)
      ->then_fail("Failed to get item in 10 seconds"),
      
    (async sub {
      while(1) {
        my $result = await GET_ITEM();
        return $result if $result->is_success;
      }
    })->(),
  );
}

Remember that the return inside the foreach loop makes its containing function return, i.e. the anonymous async sub that is invoked as the second argument to Future->wait_any. This makes it convenient to finish the loop there.