NAME
Punk::AsyncAwait - async and await in Punk apps, controllers and models
SYNOPSIS
The keywords are lexical, so every file that uses them says so. That is three files, not one:
package MyApp; # the app
use Punk;
use Punk::AsyncAwait;
get '/rooms' => async sub {
my ($c) = @_;
my $rooms = await $c->model('Message')->rooms;
return $c->json({ rooms => $rooms });
};
package MyApp::Controller::API::Message; # a controller
use Punk::Controller;
use Punk::AsyncAwait;
async sub listRooms {
my ($c) = @_;
my $rooms = await $c->model('Message')->rooms;
$_->{connected} = MyApp::Bus::connected($_->{room}) for @$rooms;
return { rooms => $rooms };
}
package MyApp::Model::Message; # a model
use Punk::Model;
use Punk::AsyncAwait;
DESCRIPTION
Punk hands back a Punk::Future from anything asynchronous, and its dispatcher already awaits a future a handler returns. This module adds the other half: await inside the handler, so a chain of then callbacks becomes straight-line code.
# before
return $c->model('Book')->search({})->then(sub {
my ($page) = @_;
$c->render('book/list', { books => $page->{rows} });
});
# after
my $page = await $c->model('Book')->search({});
return $c->render('book/list', { books => $page->{rows} });
There is nothing to configure and no plugin to register. The keywords come from Future::AsyncAwait; this module is the one line that points them at Punk's own future class so an async sub that never suspends does not drag in CPAN Future, which Punk only recommends.
IMPORT
use Punk::AsyncAwait;
use Punk::AsyncAwait future_class => 'Future'; # override
no Punk::AsyncAwait; # off for the rest of the scope
Arguments pass through to Future::AsyncAwait, after a default of future_class => 'Punk::Future' that your own future_class overrides. :experimental(cancel) and friends work as they do there.
WHAT TO EXPECT
It is per-file
use Punk::AsyncAwait in the application class does nothing for your controllers. The keywords are lexically scoped, like strict: every file that writes async or await imports them itself.
Awaiting off the loop
An async sub that suspends needs something to resume it. On a Hyperman worker that is the loop. Anywhere else there is nothing to drive the future, and the await dies with
Punk::Future: await on a pending future with no event loop to drive it
which is the deadlock reported rather than hung. It means the handler suspended on a server that cannot resume it: run under hyperman, or do not suspend. Note the message names the future, not the server.
$c->await is a different thing
Punk::Context's $c->await($future) blocks and pumps the loop; the await keyword suspends the enclosing async sub and returns to it later. Both are legal in the same scope and they do not collide - the method is a method call, the keyword is a keyword. Inside an async sub, prefer the keyword.
Awaiting a future from elsewhere
Awaiting a future of another class - a Fetch::Future, a DBIx::Loop future, a CPAN Future - is fine, and the async sub then returns a future of that class rather than a Punk::Future. Punk's dispatcher accepts any future-compatible value, so a handler still works either way.
Abandoned futures
A suspended async sub holds the future it is waiting on, which holds the reaction that will resume it. A future that is never settled therefore keeps the whole chain alive. Cancel a future you have given up on, rather than dropping it.
SEE ALSO
Punk::Future, Future::AsyncAwait, Punk::Controller, Punk::Model
AUTHOR
LNATION <email@lnation.org>
LICENSE AND COPYRIGHT
This software is Copyright (c) 2026 by LNATION <email@lnation.org>.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)