Five sequential API calls of 180 milliseconds each is nine hundred milliseconds of a request spent waiting, and they had no dependency on each other.
$responses = Http::pool(fn (Pool $pool) => [
$pool->as('stock')->get($base . '/stock/' . $sku),
$pool->as('price')->get($base . '/price/' . $sku),
$pool->as('reviews')->get($base . '/reviews/' . $sku),
]);
$responses['stock']->json();
// and the failure mode: one of them failing does not
// throw. every response must be checked.
Naming the requests with as is what makes the result readable, since the indexed form returns them positionally and a reordering silently swaps two variables. The error handling is the part that gets skipped: a pooled request that fails produces a ConnectionException in the array rather than throwing, so a loop asserting successful() on each is not optional. The concurrency is real — it is Guzzle’s curl multi handle underneath.