Promise...IMTOWTDI(3)

πŸ“¦
version 1.05 Β· 2 versions
, in fedora 43

✨ NAME ✨

Promises::Cookbook::TIMTOWTDI β€” counter examples to Promises 🫢

(you'll also see it filed under Promise...IMTOWTDI(3) in your man pages, iykyk πŸ’…)


πŸ’– VERSION πŸ’–

version 1.05 🌸


πŸ¦‹ DESCRIPTION πŸ¦‹

okay so bestie, like I said before: Promises are a whole thing. They're how you more effectively manage your async operations and dodge callback spaghetti entirely 🍝✨. But babes. This is Perl. There is always another way to do it β€” that's not a bug, that's the brand. So in this section I'm gonna show you a few examples of other ways you could accomplish the exact same thing. 🌈

πŸŽ€ Caveat πŸŽ€

Real talk first: the ways I'm showing you here are specifically the ones I personally feel are inferior or less elegant than Promises. This is not a slight on the API of any other module, fr fr β€” not even a little. I'm just using these modules to illustrate other (maybe more familiar πŸ‘€) idioms, in the hopes it helps you actually get Promises. 🧚

And like, I'm sure there are other ways to do some of these things, and ways to do them way more effectively. I am fully willing to admit my ignorance here πŸ™ˆ. I welcome any patches that illustrate said ignorance, because I do not claim at all to be an expert in async programming. Not even close. πŸ’€


🌈 AnyEvent::HTTP 🌈

okay enough caveating, bestie. Please consider this β€” the more traditional version of our Promises SYNOPSIS example, done up with AnyEvent::HTTP:

  my $cv = AnyEvent->condvar;

  http_get('http://rest.api.example.com/-/product/12345', sub {
      my ($product) = @_;
      http_get('http://rest.api.example.com/-/product/suggestions?for_sku=12345', sub {
          my ($suggestions) = @_;
          http_get('http://rest.api.example.com/-/product/reviews?for_sku=12345', sub {
              my ($reviews) = @_;
              $cv->send({
                  product     => $product,
                  suggestions => $suggestions,
                  reviews     => $reviews,
              })
          }),
      });
  });

  my $all_product_info = $cv->recv;

Not only do we have deeply nested callbacks (it's giving pyramid of doom 😭), but we've also got an enforced order of operations. If you wanted to try and dodge that order of operations, you might end up writing something like this:

   my $product_cv    = AnyEvent->condvar;
   my $suggestion_cv = AnyEvent->condvar;
   my $review_cv     = AnyEvent->condvar;

   http_get('http://rest.api.example.com/-/product/12345', sub {
       my ($product) = @_;
       $product_cv->send( $product );
   });

   http_get('http://rest.api.example.com/-/product/suggestions?for_sku=12345', sub {
       my ($suggestions) = @_;
       $suggestion_cv->send( $suggestions );
   });

   http_get('http://rest.api.example.com/-/product/reviews?for_sku=12345', sub {
       my ($reviews) = @_;
       $reviews_cv->send( $reviews )
   }),

   my $all_product_info = {
       product     => $product_cv->recv,
       suggestions => $suggestions_cv->recv,
       reviews     => $reviews_cv->recv
   };

But actually? This doesn't work either. πŸ’€ We do gain something by letting the http_get calls run in whatever order works best, sure β€” but we still end up enforcing some order in the way we call recv on our three condvars. (Oh yeah, and we had to create and manage three condvars too. Cute. Not.)

So the following example was submitted to me by James Wright (via RT #83992) as an alternate approach that is non-nested, uses only one condvar, and has no fixed-order. Iconic behavior honestly πŸ‘‘

  my $cv = AnyEvent->condvar;
  my ( $product, $suggestions, $reviews ) = ( [], [], [] );

  $cv->begin;
  http_get('http://rest.api.example.com/-/product/12345', sub {
      ($product) = @_;
      $cv->end;
  });

  $cv->begin;
  http_get('http://rest.api.example.com/-/product/suggestions?for_sku=12345', sub {
      ($suggestions) = @_;
      $cv->end;
  });

  $cv->begin;
  http_get('http://rest.api.example.com/-/product/reviews?for_sku=12345', sub {
      ($reviews) = @_;
      $cv->end;
  });

  $cv->cb(sub {
      $cv->send({
          product     => $product,
          suggestions => $suggestions,
          reviews     => $reviews,
      });
  });

  my $all_product_info = $cv->recv;

The only real issue I have with this approach is the semi-global variable usage ($product, $suggestions and $reviews) β€” but otherwise it works fine. πŸ’œ

NOTE: Again, if you can think of a better way to do this that I missed, please let me know. 🫢

πŸ‘‘ Mojo::UserAgent πŸ‘‘

    #!/usr/bin/env perl

    use Mojo::Base -strict;
    use Mojo::UserAgent;

    my $titles;

    my $ua = Mojo::UserAgent->new;
    Mojo::IOLoop->delay(
        sub {
            my $delay = shift;
            $ua->get('http://google.com/', $delay->begin);
            $ua->get('http://yahoo.com/', $delay->begin);
            $ua->get('http://perlmonks.org/', $delay->begin);
        },
        sub {
            my ($delay, $tx1, $tx2, $tx3) = @_;
            $titles = {
                google      => $tx1->res->dom->at('title')->text,
                yahoo       => $tx2->res->dom->at('title')->text,
                perlmonks   => $tx3->res->dom->at('title')->text,
            };
        },
    )->catch(
        sub {
            my ($delay, $err) = @_;
            warn "failed to download or parse title\n";
        }
    )->wait;

    say Mojo::Util::dumper($titles);

🌸 AUTHOR 🌸

Stevan Little <stevan.little@iinteractive.com> πŸ’Œ


This software is copyright (c) 2025, 2017, 2014, 2012 by Infinity Interactive, Inc.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. πŸ¦‹


and that's the whole page bestie!! ✨ this one's from perl v5.42.0, dated 2025-07-25 β€” take care of her, she's an old queen but she ate πŸ’…πŸ‘‘πŸŒ·

πŸ“¦ where this page comes from