How to process GuzzleHTTP async requests without blocking?
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
Guzzle async calls improve throughput by allowing multiple HTTP requests to be in flight at once. The common confusion is the phrase non blocking in PHP, because each PHP worker is still single request oriented and eventually must wait for completion. The practical goal is to avoid waiting per request and instead wait once for a group.
How Guzzle Async Actually Works
requestAsync returns a promise immediately. Internally, Guzzle schedules the transfer on a handler such as cURL multi, then resolves or rejects the promise later. Your code can attach callbacks and continue preparing other work before collecting all results.
This is still one blocking wait, but it is much faster than three sequential waits.
Process Many Requests with Concurrency Limits
Unbounded parallelism can overload upstream services or your own worker memory. Use Each::ofLimit to cap in flight operations while still benefiting from async scheduling.
A concurrency cap protects both your application and partner APIs.
Avoid Blocking User Facing Paths
In PHP FPM, the request worker remains occupied until your script ends. Even if internal HTTP calls are async, waiting for them still delays the client response. Move heavy fan out operations to queue workers when possible.
Typical web pattern:
- Accept request and validate data
- Push job to queue
- Return immediate response
- Let worker perform Guzzle async batch and persist results
This architecture achieves true user perceived non blocking behavior.
Error Handling and Observability
Attach clear rejection handlers and record per endpoint failures. Async failures can be partial, so avoid all or nothing assumptions. Log request id, URL, status code, and elapsed time for each promise result.
Common Pitfalls
A common mistake is calling wait() right after each requestAsync, which removes all concurrency benefits. Create all promises first, then wait once.
Another issue is no timeout policy. Without timeout and connect_timeout, one slow upstream can hold the batch far longer than expected.
A third issue is unlimited concurrency. Large fan out sets can hit file descriptor limits or trigger partner rate limits. Always define a reasonable concurrency limit and retry policy.
Finally, async inside a synchronous user request is often misunderstood as true background execution. For long tasks, use queue workers to decouple HTTP response time from external API latency.
Summary
- Guzzle async improves throughput by overlapping network waits
- Build promise groups first, then call one collection wait
- Use concurrency limits to protect resources and partner services
- Add timeout, retries, and per request logging for reliability
- For true non blocking user flow, move long async work to background workers
Related reading
- How to program with C API library on Windows using Bazel?
- How to programmatically check if Kafka Broker is up and running in Python
- How to properly and completely close/reset a TcpClient connection?
- How to Properly Combine TensorFlow's Dataset API and Keras?
- How to promisify correctly JSON.parse method with bluebird
- How to properly fire a callback after an animation and ajax calls have been successfully completed?
- How to properly make a http web GET request
- How to proxy a HTTP video stream to any amount of clients through a Spring Webserver

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.