Http::pool(): Fast Concurrent Requests Without Async Complexity
Say your dashboard needs data from four different external services: weather, exchange rates, a shipping carrier's tracking status, and an inventory partner's stock feed. The naive version looks like this:
$weather = Http::get('https://api.weather.example/current');
$rates = Http::get('https://api.rates.example/latest');
$tracking = Http::get('https://api.carrier.example/track/123');
$stock = Http::get('https://api.partner.example/stock');
Each of those calls blocks until it finishes before the next one even starts. If each one takes 300ms, you're looking at 1.2 seconds of pure waiting, in sequence, for data your app could have been fetching all at once.
The fix: Http::pool()
Laravel's HTTP client can fire requests concurrently and wait for all of them together, without you touching queues, jobs, or anything resembling real async programming.
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
$responses = Http::pool(fn (Pool $pool) => [
$pool->as('weather')->get('https://api.weather.example/current'),
$pool->as('rates')->get('https://api.rates.example/latest'),
$pool->as('tracking')->get('https://api.carrier.example/track/123'),
$pool->as('stock')->get('https://api.partner.example/stock'),
]);
$weather = $responses['weather']->json();
$rates = $responses['rates']->json();
$tracking = $responses['tracking']->json();
$stock = $responses['stock']->json();
Under the hood this uses Guzzle's concurrent request handling, so the four calls go out at roughly the same time and the whole block finishes in however long the slowest one takes, not the sum of all four. That 1.2 second sequential wait becomes closer to 300ms.
The as() method is what makes this pleasant to work with. Without it, $responses is just a plain array in the order you defined the requests, which works fine for two or three calls but gets confusing fast once you're juggling more.
Handling failures inside a pool
Individual requests inside a pool can fail independently, and by default Http::pool() won't throw for you, it just gives you back whatever response (or exception) each one produced. Check each result explicitly:
$responses = Http::pool(fn (Pool $pool) => [
$pool->as('weather')->get('https://api.weather.example/current'),
$pool->as('rates')->get('https://api.rates.example/latest'),
]);
foreach (['weather', 'rates'] as $key) {
if ($responses[$key] instanceof \Throwable) {
report($responses[$key]);
continue;
}
if ($responses[$key]->failed()) {
Log::warning("{$key} request failed", ['status' => $responses[$key]->status()]);
}
}
That's an important difference from a normal Http::get() call chained with throw(). In a pool, you almost never want one flaky third-party API to blow up an entire batch of otherwise-successful requests, so Laravel leaves failure handling up to you rather than throwing automatically.
Where this earns its keep
Aggregating data from multiple microservices for one API response is the textbook use case, but it shows up in plenty of everyday spots too: warming several cache keys at once during a deploy hook, checking the health of multiple downstream services for a status page, or fetching pricing from three competing suppliers before picking the best one. Anywhere you'd otherwise write a foreach loop of blocking HTTP calls is a candidate.
It's not a replacement for queued jobs. If a request might take 30 seconds or you don't need the result synchronously, that's still a job's job. Http::pool() is for the narrower, more common case: you need several independent responses right now, in this request, and you don't want their latencies stacking up one after another for no good reason.