App-Raider

 view release on metacpan or  search on metacpan

.claude/skills/perl-io-async-future/SKILL.md  view on Meta::CPAN

                                   ->then_fail('timeout'));

# Observe without composing (does NOT chain — return value ignored)
$f->on_done(sub { warn "done: @_" });
$f->on_fail(sub { warn "failed: $_[0]" });
```

**`then` vs `on_done`:**
- `then` returns a *new* Future; the callback returns a Future to chain. Use for control flow.
- `on_done` returns the *same* Future; callback return value is discarded. Use for side-effects/observation.

**Protective composition** (Future ≥ 0.51): pass extra siblings via `also =>` to keep them alive without making cancellation propagate:
```perl
Future->needs_all($f1, also => $f2, $f3);
```

---

## Pattern 5 — `->retain` for Fire-and-Forget

When you legitimately want to start an op and not wait, but still need it to *finish*:

```perl
$self->_log_async($msg)
    ->on_fail(sub { warn "log failed: $_[0]" })
    ->retain;
```

`->retain` parks the Future on an internal global until it completes, then drops it. Without `retain`, the chain has no holder and gets GC'd before it runs. Use this only when you genuinely don't need the result — otherwise hold the Future yourself...

---

## Pattern 6 — Future::AsyncAwait (`async`/`await`)

```perl
use Future::AsyncAwait;

async sub fetch_user {
    my ($self, $id) = @_;
    my $row = await $self->db->query("SELECT ...", $id);
    my $perms = await $self->perms->for($id);
    return { %$row, perms => $perms };
}

# Caller MUST hold the returned Future:
my $f = $client->fetch_user(42);
my $user = await $f;        # ok
# OR:
my $user = await $client->fetch_user(42);   # also ok — await holds it
```

**Pitfalls:**
- **Returning a Future from `async sub` without `await`** double-wraps it. Final expression `return await $f`, not `return $f`.
- **`@_` is not preserved across `await` on Perl < 5.24.** Unpack args into lexicals at the top of the sub.
- **`await` inside `map`/`grep` does not work.** Convert to a `for` loop with an accumulator.
- **Caller drops the Future** → "Suspended async sub … lost its returning future". The async sub's continuation is destroyed mid-flight. Always store the Future or `await` it.
- The async sub itself does not retain its returning Future. **Storing the Future in the calling object is the cure**, not adding `->retain` inside the async sub.

---

## Pattern 7 — Timeouts

```perl
# delay_future returns a Future that resolves with no value after N seconds
my $timer = $self->loop->delay_future(after => 5);

# Race the work against the timer
my $result = await Future->wait_any(
    $work_future,
    $timer->then_fail('timeout'),
);
```

`then_fail($msg)` is shorthand for "when the upstream Future is done, fail with this message" — perfect for timeouts. There's also `->timeout($secs)` on Future ≥ 0.42.

---

## Pattern 8 — Cancellation Hygiene

```perl
# Before starting a new attempt, kill stale ones
if (my $f = delete $self->{_connect_future}) {
    $f->cancel unless $f->is_ready;
}
```

- `->cancel` is idempotent but only meaningful on a non-ready Future.
- `needs_all` cancels siblings on first failure; `wait_any` cancels losers on first success. You usually don't need to cancel manually inside a composition.
- A cancelled Future is *neither* done nor failed — `is_ready` is true but `result`/`failure` will throw. Check `is_cancelled` if you need to distinguish.

---

## Pattern 9 — Loops & Tests

```perl
use IO::Async::Loop;
my $loop = IO::Async::Loop->new;
$loop->add($client);

# Block-wait on a Future from sync code (test code, main script):
my @result = $client->connect->get;

# Run loop until a Future is ready (older style):
$loop->await($f);
```

**Test pattern:**
```perl
use Test::More;
use IO::Async::Test;
use IO::Async::Loop;

my $loop = IO::Async::Loop->new;
testing_loop($loop);                # IO::Async::Test hook

my $client = MyClient->new(...);
$loop->add($client);

my $f = $client->do_thing;
wait_for_future($f);                # spins the loop
ok($f->is_done, 'completed');



( run in 1.512 second using v1.01-cache-2.11-cpan-2398b32b56e )