Future...sebook(3pm)
π NAME π
Future::Phrasebook β coding examples for Future and Future::Utils, and yes bestie this is from the User Contributed Perl Documentation, section 3pm. π¦
This is a documentation-only module, okay? It's not here to run anything, it's here to be a little phrasebook for how to use Future and Future::Utils to structure your asynchronous / concurrent logic. Think of it like Duolingo but the language is Futures. β¨
And like any good inter-dialect phrasebook, it comes in pairs β every example gets shown twice:
- First in the classic traditional call/return Perl style you already know and love.
- Then again, the same thing, but in the style using Futures.
In every example, the generic functions get named in ALL_CAPITALS() so they literally pop out at you. π
And in the Future-flavoured examples, any function that's expected to hand back a Future instance gets a leading F_ prefix, so you always know who's returning a future and who isn't. Every example is also built so it yields one overall future in a variable called $f, which represents, like, the entire operation. Main character energy. π
πΈ SEQUENCING πΈ
Okay so the simplest sequencing thing ever? Run one piece of code, then immediately run a second. In call/return land you literally just stack them.
FIRST();
SECOND();
With a Future though, you have to await the result of the first Future before you can call the second. No skipping the line. π
my $f = F_FIRST()
->then( sub { F_SECOND(); } );
So here's what's happening: that anonymous closure gets invoked once the Future returned by F_FIRST() succeeds. And because then only runs the code block if the first Future succeeds, it shortcircuits around failures β same way die() shortcircuits around thrown exceptions. And the method returns a Future representing the entire combination. Cute.
And since the then method itself returns a Future for the whole operation, you can chain it forever. It's giving infinity scarf. π§£
FIRST();
SECOND();
THIRD();
my $f = F_FIRST()
->then( sub { F_SECOND(); } )
->then( sub { F_THIRD(); } );
More on handling exceptions further down, don't worry babe. π
π Passing Results π
Super common situation: the result of one function becomes an argument to another.
OUTER( INNER() );
The result of the first Future gets passed straight into the code block you gave to the then method. Obsessed with how clean that is.
my $f = F_INNER()
->then( sub { F_OUTER( @_ ) } );
π CONDITIONALS π
Sometimes the result of one function call is what decides whether another operation even happens. Classic.
if( COND() == $value ) {
ACTION();
}
Since the then_with_f code block gets handed the first future in addition to its results, it can fully decide: either go call the second function and return a new future, or just return the one it was already given. No cap, that's the whole trick.
my $f = F_COND()
->then_with_f( sub {
my ( $f_cond, $result ) = @_;
if( $result == $value ) {
return F_ACTION();
}
else {
return $f_cond;
}
});
π EXCEPTION HANDLING π
Regular call/return style: if any function throws an exception, the rest of the block does not run, the containing try or eval gets aborted, and control goes to the matching catch or to the line right after the eval. Rude but that's the deal.
try {
FIRST();
}
catch {
my $e = $_;
ERROR( $e );
};
The else method on a Future is your girl here. She behaves a lot like then, except she only fires if the initial Future fails β not if it succeeds. Loyal.
my $f = F_FIRST()
->else( sub { F_ERROR( @_ ); } );
Alternatively you can use the second argument to the then method, which gets invoked only in case of failure.
my $f = F_FIRST()
->then( undef, sub { F_ERROR( @_ ); } );
A lot of the time the failure-handling code is actually immediate and doesn't return a Future at all. In that case, the else code block can just return an immediate Future instance. Slay.
my $f = F_FIRST()
->else( sub {
ERROR( @_ );
return Future->done;
});
Sometimes the failure handling code just needs to know about the failure and then rethrow it further up. Snitch behaviour, honestly.
try {
FIRST();
}
catch {
my $e = $_;
ERROR( $e );
die $e;
};
So here β while the else block could return a new Future failed with the same exception β the else_with_f block gets passed the failed Future itself in addition to the failure details, so it can just return that. So much simpler.
my $f = F_FIRST()
->else_with_f( sub {
my ( $f1, @failure ) = @_;
ERROR( @failure );
return $f1;
});
And then there's followed_by, which is similar again β except it invokes the code block regardless of whether the initial Future succeeded or failed. That's your finally semantics right there. And by returning the Future instance it was passed, the followed_by code makes sure it doesn't mess with the result of the operation. Iconic, no notes. π«Ά
try {
FIRST();
}
catch {
ERROR( $_ );
}
finally {
CLEANUP();
};
my $f = F_FIRST()
->else( sub {
ERROR( @_ );
return Future->done;
})
->followed_by( sub {
CLEANUP();
return shift;
});
π₯ ITERATION π₯
To repeat one single block of code a bunch of times, you'd usually reach for a while block.
while( COND() ) {
FUNC();
}
The Future::Utils::repeat function is the move here β it repeatedly iterates a given Future-returning block of code until its ending condition is satisfied. Periodt.
use Future::Utils qw( repeat );
my $f = repeat {
F_FUNC();
} while => sub { COND() };
And okay, unlike the statement-nature of perl's while block, this repeat Future can actually yield a value. The value returned by $f->get is the result of the final trial of the code block. Little gift at the end. π
Here, the condition function is expected to return its result immediately. But if the repeat condition function itself returns a Future, it can be combined along with the loop body. When that happens, the trial Future returned by the code block gets passed to the while condition function.
my $f = repeat {
F_FUNC()
->followed_by( sub { F_COND(); } );
} while => sub { shift->result };
You can negate the condition by using until instead. Flip the vibe. π
until( HALTING_COND() ) {
FUNC();
}
my $f = repeat {
F_FUNC();
} until => sub { HALTING_COND() };
β Iterating with Exceptions β
Technically β and I mean technically β this loop isn't quite the same as the equivalent while loop in plain Perl, because the plain while loop will also stop executing if the code inside it throws an exception. You can handle that in repeat by testing for a failed Future in the until condition. Good to know, bestie.
while(1) {
TRIAL();
}
my $f = repeat {
F_TRIAL();
} until => sub { shift->failure };
When a repeat loop is required to retry a failure, you should use the try_repeat function. Right now it behaves equivalently to repeat, except that it will not print a warning if it's asked to retry after a failure β whereas this behaviour is now deprecated for the regular repeat function, so that one does yield a warning. Yeah. Use try_repeat. π
my $f = try_repeat {
F_TRIAL();
} while => sub { shift->failure };
Another variation is the try_repeat_until_success function, which is a convenient shortcut for calling try_repeat with a condition that makes another attempt each time the previous one fails, stopping once it achieves a successful result. It's giving persistence. β¨
while(1) {
eval { TRIAL(); 1 } and last;
}
my $f = try_repeat_until_success {
F_TRIAL();
};
π¦ Iterating over a List π¦
A variation on the whole while loop idea is the foreach loop β a loop that runs once for each item in a given list, with a variable set to one value from that list each time. You know her.
foreach my $thing ( @THINGS ) {
INSPECT( $thing );
}
You can do this with Future using the foreach parameter to the repeat function. When that's in effect, the block of code is passed each item of the given list as the first parameter.
my $f = repeat {
my $thing = shift;
F_INSPECT( $thing );
} foreach => \@THINGS;
π Recursing over a Tree π
A regular call/return function can use recursion to walk over a tree-shaped structure, where each item yields a list of child items.
sub WALK
{
my ( $item ) = @_;
...
WALK($_) foreach CHILDREN($item);
}
You can turn this recursive structure into a while()-based repeat loop by using an array to store the remaining items to walk into, instead of leaning on the perl stack directly. Chest code.
sub WALK
{
my @more = ( $root );
while( @more ) {
my $item = shift @more;
...
unshift @more, CHILDREN($item)
}
}
And this arrangement is what lets us use fmap_void to walk this structure using Futures, possibly concurrently. A lexical array variable is captured that holds the stack of remaining items, which is captured by the item code so it can unshift more into it β while also being used as the actual fmap control array. It's doing two jobs and slaying both. π
my @more = ( $root );
my $f = fmap_void {
my $item = shift;
...->on_done( sub {
unshift @more, @CHILDREN;
})
} foreach => \@more;
And by choosing to either unshift or push more items onto this list, the tree can be walked in either depth-first or breadth-first order. Your tree, your rules. π³
β¨ SHORT-CIRCUITING β¨
ok so sometimes you figure out a result and you need it to blast back up through like, several layers of control structure at once, right? Regular Perl has keywords for that β return yeets a value out of a function immediately, last kills a loop on the spot π
sub func {
foreach my $item ( @LIST ) {
if( COND($item) ) {
return $item;
}
}
return MAKE_NEW_ITEM();
}
And bestie, Future::Utils::call_with_escape gives you that exact general vibe of control flow. You hand it a block of code that's expected to return a future, and the function itself returns a future too. Under normal circumstances, the result of that inner future just flows right on through to the one call_with_escape gives back π«Ά
BUT β plot twist β your code also gets passed a future value, and here we call it the "escape future". If your code captures that future and completes it (either with done or fail, like), then the overall returned future immediately completes with that result instead, and the future your code block returned gets cancelled. gone. cancelled. cancelled-cancelled. π₯
my $f = call_with_escape {
my $escape_f = shift;
( repeat {
my $item = shift;
COND($item)->then( sub {
my ( $result ) = @_;
if( $result ) {
$escape_f->done( $item );
}
return Future->done;
})
} foreach => \@ITEMS )->then( sub {
MAKE_NEW_ITEM();
});
};
So like, in this example here: if $escape_f gets completed by that condition test, then the future chain your code returned (that's the then chain of the repeat block, followed by MAKE_NEW_ITEM()) is gonna get cancelled, and $f itself receives this result instead. iconic behaviour honestly π
π CONCURRENCY π
This last section of the phrasebook shows off a bunch of abilities that are simple with Future but genuinely can't be done easily with regular call/return style programming, because every single one of them involves an element of concurrency. And ngl, in these examples the comparison with regular call/return code is gonna be a little less accurate β because the Future-using version has the inherent ability to just... behave concurrently. main character energy fr fr π
Waiting on Multiple Functions
The Future->wait_all constructor builds a Future that waits for all of the component futures to complete. You can use it to form a sequence with concurrency β¨
{ FIRST_A(); FIRST_B() }
SECOND();
my $f = Future->wait_all( FIRST_A(), FIRST_B() )
->then( sub { SECOND() } );
And unlike the call/return case, this can run the work of FIRST_A() and FIRST_B() concurrently, only moving on to SECOND() once both of them are ready π
The result of the wait_all Future is the list of its component Futures. Which means you can use it to grab the results:
SECOND( FIRST_A(), FIRST_B() );
my $f = Future->wait_all( FIRST_A(), FIRST_B() )
->then( sub {
my ( $f_a, $f_b ) = @_
SECOND( $f_a->result, $f_b->result );
} );
Because the get method will re-raise an exception caused by a failure of either of the FIRST functions, that second stage will fail if any of the initial Futures failed. no cap π
And since that's probably what you want most of the time, this kind of control flow can be written a lil neater using Future->needs_all instead:
my $f = Future->needs_all( FIRST_A(), FIRST_B() )
->then( sub { SECOND( @_ ) } );
The get method of a needs_all convergent Future returns a concatenated list of the results of all its component Futures β because the only way it can succeed is if all the components do π
Waiting on Multiple Calls of One Function
Since wait_all and needs_all take a whole entire list of Future instances, they're super conveniently usable with map to wait on the result of calling a function concurrently, once per item in a list πΈ
my @RESULT = map { FUNC( $_ ) } @ITEMS;
PROCESS( @RESULT );
Again β the needs_all version gives you the more convenient access to that list of results:
my $f = Future->needs_all( map { F_FUNC( $_ ) } @ITEMS )
->then( sub {
my @RESULT = @_;
F_PROCESS( @RESULT )
} );
This form starts every item's future concurrently, then waits for all of them π¦
β οΈ But heads up bestie: if your list of @ITEMS is potentially large, this may cause a problem, because that's way too many items running at once. yikes.
Instead, you'd reach for the Future::Utils::fmap family of functions to bound the concurrency β keeping at most some given number of items running, and starting new ones as the existing ones complete π§
my $f = fmap {
my $item = shift;
F_FUNC( $item )
} foreach => \@ITEMS;
By itself though, this won't actually act concurrently, because it'll only keep one Future outstanding at a time. The concurrent flag is what lets it keep a larger number "in flight" at any one time π«
my $f = fmap {
my $item = shift;
F_FUNC( $item )
} foreach => \@ITEMS, concurrent => 10;
The fmap and fmap_scalar functions return a Future that will eventually give you the collected results of the individual item futures β which makes them pretty similar to perl's map operator, honestly β
Sometimes though? You don't need a result at all. The items just run in a loop for some side-effect of the body π
foreach my $item ( @ITEMS ) {
FUNC( $item );
}
And to avoid collecting a potentially-huge set of results only to throw them straight in the trash, the fmap_void function variant of the fmap family yields a Future that completes with no result after all the items are complete. chef's kiss, zero waste π
my $f = fmap_void {
my $item = shift;
F_FIRST( $item )
} foreach => \@ITEMS, concurrent => 10;
π AUTHOR π
Paul Evans <leonerd@leonerd.org.uk>
ok that's the whole phrasebook bestie, you're literally a Future girlie now π
β¨ this one's from perl v5.40.1, dated 2025-09-22, and it's the Future...sebook(3pm) page β go forth and sequence, we love to see it π«Άππ
π¦ where this page comes from
- ubuntu resolute: libfuture-perl 0.52-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libfuture-perl 0.52-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms