PerlDancer / Dancer2

Perl Dancer Next Generation (rewrite of Perl Dancer)
http://perldancer.org/
Other
543 stars 274 forks source link

Provide support for Futures::AsyncAwait #1670

Open tomgreen98 opened 2 years ago

tomgreen98 commented 2 years ago

Just want to say, I really like Dancer2. But I hit a limitation with one area of its support for recent improvements to async style code calling.

I have been trying to use Future::AsyncAwait with the delayed DSL something like:

package MyService;

# NOTE: Running this under Twiggy to get the event loop to work.

use Dancer2;
use AnyEvent;             # for the event loop
use AnyEvent::HTTP; # for async http calls
use Promise::ES6;      # modern promise responses
use Future::AsyncAwait; # for even more modern handling of async.

post '/' => sub {
    my $url = "...";
    delayed async {

         # just a deferred promise implementation. pick anyone you prefer.
         my ( $resolve, $reject );
         my $promise = Promise::ES6->new(
             sub ( $res, $rej ) {  ( $resolve, $reject ) = ( $res, $rej ) }
         );

         # start an async call
         AnyEvent::HTTP::http_get( $url, %opts, sub { 
             my ($body, $header)  = @_;
             $header->{Status} =~ /^[23]\d\d/  ? $resolve->($body) : $reject->($body);
         });

        my $response = await  $promise;

        # Problems starts here.
        delayed {
            content($response);
            done();
        };
    }
}

1;

The problem is that i get an error after the await that the inner delayed DSL can not be used where they are located. Looks like Dancer2 does a check to see if a package variable that was localized is defined, which gets lost during the await. If i manually copy that variable and the others before the await call, I can make it work if I restore them after the await:

my $stash = stash_package_variables();
my $response = await  $promise;
restore_package_variables($stash);

The problem seems to revolve around the design of the delayed underlying objects Dancer2::Core::Response::Delayed in that this object captures the needed package variables in a block locally and then reuses the object in the subsequent delayed calls. But the design for delayed assumes that you are wrapping all your async callbacks in delayed DSL. That not possible in async/await style since the underlying callbacks are not usually exposed.

I guess technically in my example I could wrap the callback for the http_get call, but my actual implementation is with a HTTP library that already returns the promise and you should be able to use this with any library that returns promises without having to change the library to be Dancer2 aware.

Another minor issue is that the top level await can not be wrapped in async like I have it in the current example. You have to instead manually unwrap the async call like:

delayed {
     Future->unwrap(async sub {
     })->();
};

It would be better to natively support async/await style directly in the DSL and adjust the delayed design to be smarter about how to handle the transitions across the await boundaries.

tomgreen98 commented 2 years ago

Im pretty sure that my workaround of stashing of the package variables is likely to cause other problems since they are not localized correctly after that.