Asynchronous HTTP client requests are coming in Laravel 8.x
Back in March 2020, when Laravel 7 got released, a brand new HTTP client was introduced which was essentially a wrapper of Guzzle HTTP client. This made the developer experience a lot smoother and easier.
- Synchronous requests
- Concurrent asynchronous requests
- In Closing
For instance, a typical POST request using the HTTP client would look like this.
$response = Http::post('http://test.com/users', [ 'name' => 'Steve', 'role' => 'Network Administrator', ]);
As opposed to the pre-Laravel 7.x era where you would need to use Guzzle as an external library like so.
$client = new \GuzzleHttp\Client(); $request = $client->post('http://test.com/users', [ 'name' => 'Steve', 'role' => 'Network Administrator', ]); $response = $request->send();
As you can tell, the request made using HTTP client of Laravel 7.x, even though is using Guzzle under-the-hood, looks more concise and elegant.
All these requests that you make using the HTTP client, are synchronous in nature. So, if you’re calling 5 external requests, each will be called once the request before it is done getting the response. There should be some sort of asynchronous nature to these requests.
And as it turns out, Guzzle is already providing the support for asynchronous requests in the form of Guzzle/Promises.
So, it was only a matter of implementing it in Laravel’s HTTP client.
And this recent PR exactly tries to do the same.
Concurrent asynchronous requests
As I mentioned previously, this PR by Andrea Marco Sartori is bringing concurrency while sending asynchronous requests with the Laravel HTTP client by using Guzzle/Promises under-the-hood. The PR will be included in the next release of Laravel 8.x.
There are essentially two ways you can make asynchronous requests using this.
- Asynchronous requests using
- A pool to handle multiple asynchronous requests concurrently
Asynchronous requests using
If you want to create an asynchronous request, you can create it like so.
$promise = Http::async()->get($url);
As you can tell, you need to chain the
async() method on the Http client before making the request. But instead of returning a response, this will return a promise which you can resolve/reject.
So, how will you get the response? You can further chain the
then() method based on whether a promise is fulfilled or rejected like so.
$promise = Http::async()->get($url)->then( fn (Response|TransferException $result) => $this->handleResult($result) );
As you can tell, the callback in the
then() method can receive the following based on the promise’s status.
- An instance of
Illuminate\Http\Client\Responseif the promise is fulfilled
- An instance of
Illuminate\Http\Client\Responseif the promise is rejected but a response was provided (
- An instance of GuzzleHttp\Exception\TransferException if the promise is rejected with no response (e.g. timeout)
And that is how you can get the response of an asynchronous request.
The pool for multiple async requests
If you want to make multiple async requests at the same time, you can use the pool like so.
use Illuminate\Http\Client\Pool; // the waiting time will be ~6 seconds instead of 12 $responses = Http::pool(fn (Pool $pool) => [ $pool->get('https://httpbin.org/delay/6')->then(/* ... */), $pool->get('https://httpbin.org/delay/3')->then(/* ... */), $pool->get('https://httpbin.org/delay/3')->then(/* ... */), ]); $responses->ok(); $responses->successful();
Since all of the requests in the pool are asynchronous, the slowest request determines the maximum waiting time for all promises to be fulfilled.
Asynchronous requests can also be added to the pool with a key:
$responses = Http::pool(fn (Pool $pool) => [ $pool->add('foo')->get('https://httpbin.org/delay/6')->then(/* ... */), $pool->add('bar')->get('https://httpbin.org/delay/3')->then(/* ... */), $pool->add('baz')->get('https://httpbin.org/delay/3')->then(/* ... */), ]); $responses['foo']->ok(); $responses['bar']->successful(); $connectionFailed = $responses['baz'] instanceof GuzzleHttp\Exception\ConnectException;
Responses are instances of
Illuminate\Http\Client\Response if requests were responded (
5xx). Otherwise, if no response was received, the exception that provoked the promise rejection is returned.
And this is how you will be able to make asynchronous requests in the future Laravel versions. This will significantly reduce the response time if you are making multiple requests to third-party APIs and hence will improve the user experience.
You can learn more about this feature in this PR.