Currently Available: Need a skilled Software Developer for your next project?
Categories
Laravel

Making Parallel Requests with Laravel HTTP Pools

When a Laravel service needs data from several external APIs, it can use Http::pool() to send the HTTP requests together. Laravel starts the requests so their network waits overlap, then returns the results to the PHP code that called Http::pool().

Parallel requests help when each call can start without waiting for another. If one request needs another request’s response, the code must wait for that response first. The caller also waits for all requests in the pool to finish. The examples below show how to define and limit a pool, identify its results, and handle failures.

What Laravel does with a pool

Pass Http::pool() a closure, a function Laravel calls to build the list of requests. The closure receives a Pool object, which provides methods for defining those requests. Laravel dispatches the pooled requests concurrently and returns an array after they finish. Here, concurrently means that network waits overlap; it does not mean PHP runs CPU-heavy work on multiple cores. Laravel’s HTTP client documentation describes this pattern, and the client implementation uses asynchronous requests before waiting for results.

use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;

$responses = Http::pool(fn (Pool $pool) => [
    $pool->get('https://catalog.example.test/items/42'),
    $pool->get('https://pricing.example.test/items/42'),
    $pool->get('https://inventory.example.test/items/42'),
]);

The example uses separate services to retrieve catalog, pricing, and inventory data. Because none of these requests needs another request’s response, Laravel can start them together. If the pricing request needs an identifier returned by the catalog API, the code must wait for the catalog response before it can define the pricing request.

The caller waits for Http::pool() to return its result array after all the requests finish. One slow request can keep the caller waiting even after the other responses have arrived.

When parallel requests help

Use a pool when a page or other request handler needs results from several independent calls to remote services. For example, a page that needs catalog details, a price, and inventory status can request data from each service at the same time instead of waiting for one call to finish before starting the next.

If the code waits for each call to finish before starting the next, the wait times roughly add together. If Laravel sends independent calls together, the total wait is closer to that of the slowest request, plus local and network overhead. This explains how parallel requests could reduce latency; it does not guarantee a speedup or report a measured performance result.

Pools suit independent requests that the caller needs before it can continue. They do not suit dependent calls, CPU-heavy work, background jobs, or cases where the caller must act as soon as one particular response arrives. Laravel documents HTTP batches separately for deferred execution and callbacks such as progress handling.

Limit concurrency and configure each request

The concurrency argument limits how many requests Laravel has sent but not yet finished at one time. For example, a pool of three requests with concurrency: 2 sends at most two at a time:

$responses = Http::pool(
    fn (Pool $pool) => [
        $pool->get('https://api.example.test/a'),
        $pool->get('https://api.example.test/b'),
        $pool->get('https://api.example.test/c'),
    ],
    concurrency: 2,
);

A limit bounds the burst of requests sent to other services, but it does not guarantee that those services will accept every request. Choose a limit based on the services’ capacity and rate limits, as well as how long the caller can wait. Laravel’s example value is not a universal recommendation. The current documentation explains how to set a limit but does not specify a default for every release, so check the documentation for the Laravel version you run.

Set headers and other options on each request in the pool. Laravel’s documentation says you cannot chain methods such as withHeaders() or middleware() onto Http::pool() itself:

$responses = Http::pool(fn (Pool $pool) => [
    $pool->withHeaders(['X-Example' => 'value'])
        ->get('https://api.example.test/a'),

    $pool->withHeaders(['X-Example' => 'value'])
        ->get('https://api.example.test/b'),
]);

Laravel’s HTTP client uses Guzzle, so check which PHP and HTTP-handler setup your deployment uses; the handler sends the requests. Guzzle’s documentation says cURL is required for concurrent requests. The sources do not establish that every custom handler behaves the same way.

Identify results and handle failures

Laravel returns an array of results. Give each request a name with as() when the responses have distinct roles:

$responses = Http::pool(fn (Pool $pool) => [
    $pool->as('catalog')
        ->get('https://catalog.example.test/items/42'),

    $pool->as('price')
        ->get('https://pricing.example.test/items/42'),
]);

$catalogResponse = $responses['catalog'];
$priceResponse = $responses['price'];

Named keys make it clear which response belongs to which request. Laravel also documents access by numeric position, but code should avoid relying on array iteration order across versions and handlers. The inspected implementation assigns results as each request finishes, so a key is a safer way to identify a result than its position in an iteration.

A result can be an HTTP response or a Throwable for a connection-level failure, such as a timeout or DNS failure. An HTTP error status, by contrast, arrives as a response that you can check with failed():

foreach ($responses as $key => $result) {
    if ($result instanceof Throwable) {
        // Handle a connection or transport failure.
        continue;
    }

    if ($result->failed()) {
        // Handle an HTTP error response.
    }
}

Choose how the calling code handles a mix of successful responses and failures. It can use the available data, apply a fallback, or fail the overall request. A pool returns results to its caller; it does not decide which response matters or how to recover from failures.

What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Email updates

Usually a new article and a few links I found interesting.

No spam. Unsubscribe with one click.

Leave a Reply

Your email address will not be published. Required fields are marked *