I’m facing an issue with a plugin I’m currently creating. The plugin consists of a single function that is called whenever a specific page is visited. When this page gets visited this function will call an external API using the Guzzle PHP Library and attempt to save the results in a field of this page.
This works when I go about it like this:
require __DIR__ . '/vendor/autoload.php';
use GuzzleHttp\Client;
function updateAPIResult($page){
$kirby = kirby();
if(!$page){return}
$client = new GuzzleHttp\Client();
$response = $client->request('POST', "https://api.openai.com/v1/chat/completions", [
'headers' => [
'Content-Type' => 'application/json',
],
'json' => [
'mydata' => 'myvalue'
]
]);
if($response->getStatusCode() !== 200){
return; // There was an error
}
$newValue = json_decode($response->getBody(), true)['response_value'];
if(!$newValue) {
return; // value not found in response
}
// all seems to be good, update the page
$kirby->impersonate('kirby');
$page = $page->update([
'api_value' => $newValue
], 'en');
$kirby->impersonate(null);
}
When this function is triggered by a user visiting this page, all works as expected. The problem I’m facing is that the API call might actually take quite a long time (10-20 seconds). The way it is set up, a user would have to wait until the response is received until they get to see the page.
I only need the user-visit to trigger the API call though. There is no need to have the data already when serving the page to the user.
That’s why I tried implementing the same behavior in an asynchronous function using Guzzles async Requests. This looks like this:
<?php
require __DIR__ . '/vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Psr\Http\Message\ResponseInterface;
function updateAPIResult($page){
$kirby = kirby();
if(!$page){return}
$client = new GuzzleHttp\Client();
$promise = $client->requestAsync('POST', "https://api.openai.com/v1/chat/completions", [
'headers' => [
'Content-Type' => 'application/json',
],
'json' => [
'mydata' => 'myvalue'
]
]);
$promise->then(
function (ResponseInterface $res) use ($kirby, $page){
$newValue = json_decode($res->getBody(), true)['response_value'];
// update page with our new value
$kirby->impersonate('kirby');
$page = $page->update([
'api_value' => $newValue
], 'en');
$kirby->impersonate(null);
},
function (RequestException $e) use ($kirby, $page) {
// logging the error in our field instead
$kirby->impersonate('kirby');
$page = $page->update([
'api_value' => $e->getMessage()
], 'en');
$kirby->impersonate(null);
}
);
}
In a test I made using https://httpbin/delay I could confirm that my page now loads instantly and won’t wait for the API request to be completed. Unfortunately none of the code inside the callback functions seems to have an effect and in my current setup I have little possibilities of debugging this other than logging values to fields.
It might be that I fundamentally don’t understand how promises work in this context, so I’m generally looking for a way to trigger a function that won’t force an actual user to wait.
Put differently: How can I run code that is triggered by user interaction independently from actually serving this user a response?