Reputation: 11
I am quite new to ReactPHP, async programming and software dev at all. I am currently working on an API that I want to make async. The API has to do massive amounts of API requests to external APIs. My idea is to start the API requests in the background so that it does not block further client requests but i cannot bring it to work properly.
In the whole API we have to do requests to several distributors that are all having different ways of handling API request, so we have to implement each distributor individually.
In the API I am using the ReactPHP EventLoop.
I am working with React\Promise\all(). Therefore I create 5 Test Promises and give them to all():
Info: I am using Guzzle to do the requests because I have to put the headers in the curl option for that specific distributor.
private function requestProduct($id, $access_token, $request_url, $client_id)
{
$request_url = str_replace(' ','',$request_url);
$data = [
'currency' => 'EUR',
'limit' => 1,
'search' => $id
];
$request_url .= "search?" . http_build_query($data);
$this->setRequestUrl($request_url);
$header = [
"content-type: application/x-www-form-urlencode",
"accept: application/json",
"authorization: Bearer $access_token",
"client_id: $client_id"
];
// $this->setHeader($header);
$this->client = new Client();
for ($i = 0; $i < 5; $i++) {
$promises[] = new Promise(function ($resolve, $reject) use ($request_url, $header, $i) {
try {
echo "Starting async request {$i} to URL: {$request_url}" . PHP_EOL;
$this->client->getAsync($request_url, [
'curl' => [
CURLOPT_HTTPHEADER => $header,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
]
])
->then(
function (ResponseInterface $response) use ($resolve, $i) {
$content = $response->getBody()->getContents();
$decodedResponse = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('Invalid JSON response');
}
echo "Request {$i} completed successfully." . PHP_EOL;
$resolve($decodedResponse);
},
function (Exception $e) use ($reject, $i) {
echo "Error during async GET request {$i}: {$e->getMessage()}" . PHP_EOL;
$reject($e);
}
);
} catch (Exception $e) {
echo "Unexpected error in request {$i}: {$e->getMessage()}" . PHP_EOL;
$reject($e);
}
});
}
echo "Waiting for all promises to resolve" . PHP_EOL;
// Wait for all promises to resolve
return all($promises)
->then(function($responses) {
$result = [];
echo "All Promises resolved" . PHP_EOL;
echo gettype($responses) . PHP_EOL;
foreach ($responses as $i => $response) {
// print_r($response);
$result[$i] = $response['status'];
echo "Result {$i} decoded" . PHP_EOL;
}
print_r($result);
return $result;
})
->catch(function($error) {
echo "An error occurred: {$error->getMessage()}" . PHP_EOL;
throw new Exception("Error in all()", 500);
});
}
The function is being called in a function called getProductByMpn.
The returned Promise from all() is supposed to be resolved in a Controller.
public function getProductByMpn($request)
{
try {
$body = $request->getParsedBody();
$distributor = DistributorHelper::getDistributorNameFromPath($request);
$distributorID = $body['linr'];
$mpn = $body['mpn'] ?? null;
if (!$distributor || !$mpn || !$distributorID) {
throw new Exception('Missing distributor, distributor ID or mpn', 400);
}
return $this->distributorService
->getProductByMpn($distributor, $mpn, $distributorID)
->then(function ($response) {
echo "Response successful". PHP_EOL;
return JsonResponse::ok($response);
})
->catch(function ($error) {
echo "Error in getProductByMpn: {$error->getMessage()}" . PHP_EOL;
throw new Exception("Error in getProductByMpn: {$error->getMessage()}", 500);
});
} catch (Exception $e) {
return JsonResponse::individual($e->getCode(), ['error' => $e->getMessage()]);
}
}
Now, the problem is that it does not resolves. Instead, it seems like it gets stuck somewhere. I think I did something wrong in resolving my Promise(s) but I can't find the error.
I also tried one API request and it works completely fine:
private function requestProduct($id, $access_token, $request_url, $client_id)
{
$request_url = str_replace(' ','',$request_url);
$data = [
'currency' => 'EUR',
'limit' => 1,
'search' => $id
];
$request_url .= "search?" . http_build_query($data);
$this->setRequestUrl($request_url);
$header = [
"content-type: application/x-www-form-urlencode",
"accept: application/json",
"authorization: Bearer $access_token",
"client_id: $client_id"
];
// $this->setHeader($header);
$this->client = new Client();
return new Promise(function ($resolve, $reject) use ($request_url, $header) {
try {
echo "Starting async request to URL: {$request_url}" . PHP_EOL;
$this->client->getAsync($request_url, [
'curl' => [
CURLOPT_HTTPHEADER => $header,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
]
])
->then(
function (ResponseInterface $response) use ($resolve) {
$content = $response->getBody()->getContents();
// echo "Response received: $content" . PHP_EOL;
$decodedResponse = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('Invalid JSON response');
}
$resolve($decodedResponse);
},
function (Exception $e) use ($reject) {
echo "Error during async GET request: {$e->getMessage()}" . PHP_EOL;
$reject($e);
}
)
->wait(); // has to stand here to resolve correctly?
} catch (Exception $e) {
echo "Unexpected error: {$e->getMessage()}" . PHP_EOL;
$reject($e);
}
});
}
The funny thing is, when I mix it together, than the all() function works fine and it prints out a result.
This is the mixed function. I think it has something to do with the wait() in the single Promise. When the Promise does not have the wait() it does not work either.
private function requestProduct($id, $access_token, $request_url, $client_id)
{
$request_url = str_replace(' ','',$request_url);
$data = [
'currency' => 'EUR',
'limit' => 1,
'search' => $id
];
$request_url .= "search?" . http_build_query($data);
$this->setRequestUrl($request_url);
$header = [
"content-type: application/x-www-form-urlencode",
"accept: application/json",
"authorization: Bearer $access_token",
"client_id: $client_id"
];
// $this->setHeader($header);
$this->client = new Client();
for ($i = 0; $i < 5; $i++) {
$promises[] = new Promise(function ($resolve, $reject) use ($request_url, $header, $i) {
try {
echo "Starting async request {$i} to URL: {$request_url}" . PHP_EOL;
$this->client->getAsync($request_url, [
'curl' => [
CURLOPT_HTTPHEADER => $header,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
]
])
->then(
function (ResponseInterface $response) use ($resolve, $i) {
$content = $response->getBody()->getContents();
$decodedResponse = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('Invalid JSON response');
}
echo "Request {$i} completed successfully." . PHP_EOL;
$resolve($decodedResponse);
},
function (Exception $e) use ($reject, $i) {
echo "Error during async GET request {$i}: {$e->getMessage()}" . PHP_EOL;
$reject($e);
}
);
} catch (Exception $e) {
echo "Unexpected error in request {$i}: {$e->getMessage()}" . PHP_EOL;
$reject($e);
}
});
}
echo "Waiting for all promises to resolve" . PHP_EOL;
// Wait for all promises to resolve
all($promises)
->then(function($responses) {
$result = [];
echo "All Promises resolved" . PHP_EOL;
echo gettype($responses) . PHP_EOL;
foreach ($responses as $i => $response) {
// print_r($response);
$result[$i] = $response['status'];
echo "Result {$i} decoded" . PHP_EOL;
}
print_r($result);
return $result;
})
->catch(function($error) {
echo "An error occurred: {$error->getMessage()}" . PHP_EOL;
throw new Exception("Error in all()", 500);
});
return new Promise(function ($resolve, $reject) use ($request_url, $header) {
try {
echo "Starting async request to URL: {$request_url}" . PHP_EOL;
$this->client->getAsync($request_url, [
'curl' => [
CURLOPT_HTTPHEADER => $header,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
]
])
->then(
function (ResponseInterface $response) use ($resolve) {
$content = $response->getBody()->getContents();
// echo "Response received: $content" . PHP_EOL;
$decodedResponse = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception('Invalid JSON response');
}
$resolve($decodedResponse);
},
function (Exception $e) use ($reject) {
echo "Error during async GET request: {$e->getMessage()}" . PHP_EOL;
$reject($e);
}
)
->wait(); // has to stand here to resolve correctly?
} catch (Exception $e) {
echo "Unexpected error: {$e->getMessage()}" . PHP_EOL;
$reject($e);
}
});
}
The result here from all() is as expected. It prints out the results of every request.
Is there another way to resolve Promises without using wait() and how do I properly resolve Promises with all()?
Upvotes: 1
Views: 63
Reputation: 1
Guzzle by default uses cURL
, but you can configure it to use a React-compatible HTTP handler (https://docs.guzzlephp.org/en/stable/faq.html):
Does Guzzle require cURL?
No. Guzzle can use any HTTP handler to send requests. This means that Guzzle can be used with cURL, PHP's stream wrapper, sockets, and non-blocking libraries like React. You just need to configure an HTTP handler to use a different method of sending requests.
Your example likely does not work because you are mixing a cURL
-based Guzzle client, which means that these requests will not get added to ReactPHP's event loop, and so they will wait forever.
Your last example works because you are calling wait
on Guzzle's promise directly, which blocks until the request is complete.
One solution would be to use a library that integrates Guzzle with ReactPHP's event loop, e.g. https://github.com/WyriHaximus/react-guzzle-psr7/.
Alternatively, you could use ReactPHP's HTTP client instead of Guzzle. It also supports setting headers (https://reactphp.org/http/#withheader):
withHeader()
The
withHeader(string $header, string $value): Browser
method can be used to add a request header for all following requests.$browser = $browser->withHeader('User-Agent', 'ACME'); $browser->get($url)->then(…);
Upvotes: 0