Wstęp
Cześć kociaczki, psiaczki i miłe zwierzaczki 😀 Dzisiaj porozmawiamy sobie tak ogólnie o bibliotece do języka PHP Guzzle Promises.
Pewnie część z tych co znają język JavaScript kojarzą taki twór jak Promise czyli tzw. Obietnicę. Do jej szybszej obsługi nawet wprowadzono w tym języku async i await co już na mówi po części do czego to służy. Tak: do asynchroniczności 😄
Jak wiadomo PHP nie jest językiem, który posiadałby asynchroniczność, ale to nie zraziło twórców Guzzle, która służy do obsługi requestów i pewnego dnia utworzyli oni Promises, aby można było obsłużyć asynchroniczne requesty – czyli taki PHPowy odpowiednik fetch znanego z JS. Jak się potem zaczytałem, to można ich Promise używać nie tylko do requestów, co mnie w sumie cieszy, bo dzięki temu możemy nasz kod wzbogacić o wiele ciekawszych mechanizmów.
Quick Start został opisany na ich repozytorium GitHub w pliku Readme.md – ja tylko przełożę część występujących tam przykładów.
Podstawy – przykłady
Utworzenie Promise
use GuzzleHttp\Promise\Promise;
// Tworzymy nasz Promise
$promise = new Promise();
// i ustalamy co ma się wykonać
// metoda then() przyjmuje dwie funkcje
// pierwsza funkcja wykonuje się gdy nie napotkamy błędów
// druga wykona się jeśli zostaną napotkane błędy/wyjątki
$promise->then(
// $onFulfilled
function ($value) {
echo 'The promise was fulfilled.';
},
// $onRejected
function ($reason) {
echo 'The promise was rejected.';
}
);
// oba argumenty funkcji then musza być typu callable:
// @see https://www.php.net/manual/en/language.types.callable.php
Rozwiązanie Promise
Aby nasz Promise cokolwiek wykonał musi zostać rozwiązany, jest to dosyć proste, bo wystarczy to zrobić:
use GuzzleHttp\Promise\Promise;
$promise = new Promise();
$promise
->then(function ($value) {
// ten then() zwróci wartość, ale nie przerwie łańcucha wywołań then()
return "Hello, " . $value;
})
// jak widzisz then() zwraca nowy Promise. Ten jako $value będzie miał to co zwrócił poprzedni then()
->then(function ($value) {
echo $value;
});
// Rozwiązanie Promise
$promise->resolve('reader.')
// wyświetli się "Hello, reader."
Ok pomyślmy co tutaj się stało 🤔 Mamy jeden zadeklarowany Promise, który ma 2x then().
Jeśli Promise nie jest rozwiązany to każdy then() zwraca nowy Promise, dzięki czemu możemy tworzyć taki łańcuch. Dla tych co chcą bardziej wiedzieć jak ten kod działa to zapraszam na GitHub: https://github.com/guzzle/promises/blob/8e7d04f1f6450fef59366c399cfad4b9383aa30d/src/Promise.php#L31 – specjalnie dla was to znalazłem 😁
Więc póki nasz Promise jest w oczekiwaniu to możemy mu dokładać coraz to bardziej kolejne then(), a w nich każde kolejne callable (czyli tak w sumie callbacki), a każdy kolejny callable będzie miał jako wartość argumentu to co jest zwracane w poprzednim callable.
W pierwszym then() mamy funkcję zwracającą połączenie „Hello, ” z podaną mu wartością. W kolejnym then() jako wartość argumentu mamy poprzednio zwróconą wartość. Z tego przykładu wiemy że to będzie w całości string (bo tak zadziała PHP w tym przypadku). Nasz drugi then() wyświetli tą wartość.
Więc to co się dzieje po $promise->resolve(’reader.’) to ten „reader.” jest przekazywany do pierwszego then(). On łączy „Hello, ” z „reader.” i to jest przekazywane do kolejnego then() i on ma do wykonania wyświetlenie już wcześniej połączonego napisu co po prostu pokaże nam „Hello, reader.”
Proste? Tak? 😅 Wiem, że nie dla każdego to się wydaje intuicyjne, ale kilka prób i okaże się, że to banał.
Promise Forwarding
Tak do końca nie wiem jak to poprawnie na Polski przetłumaczyć, no może przekazywanie obietnic ma tutaj jakiś sens, ale nie będę szukać na siłę 😉
W skrócie: możemy zadeklarować kilka obietnic i je zwracać w then() dzięki temu mamy wpływ na to, kiedy i którą obietnicę będzie można wywołać. W tym prostym przykładzie poniżej wykonamy je jedna za drugą, ale nic nie stoi na przeszkodzie, aby pomiędzy rozwiązaniami wstawić jakiś inny kod.
use GuzzleHttp\Promise\Promise;
$promise = new Promise();
$nextPromise = new Promise();
$promise
->then(function ($value) use ($nextPromise) {
echo $value;
return $nextPromise;
})
->then(function ($value) {
echo $value;
});
// Rozwiązujemy nasz pierwszy callback. Wyświetli się 'A'
$promise->resolve('A');
// tutaj może być inny kod
// Rozwiązujemy nasz drugi callback. Wyświetli się "B"
$nextPromise->resolve('B');
Synchroniczne wywołanie
Czasami (tak jak czasami w JS i innych językach bywa) mamy możliwość czekania na wykonanie się obietnicy. Czy to jest przydatne? No czasami tak, ale tutaj nie będę się nad tym rozwodzić.
Metoda wait() domyślnie rozwiązuje nam Promise i dodatkowo zwraca nam wartość z callbacka:
use GuzzleHttp\Promise\Promise;
$promise = new Promise(function () use (&$promise) {
$promise->resolve('foo');
});
// metoda wait() wymusza czekanie na rozwiązanie się Promise
echo $promise->wait(); // wyświetli "foo"
// i dopiero wtedy zacznie iść do dalszej części kodu
Podsumowanie
Dzisiaj poznaliśmy coś co w PHP było uznawane za niemożliwe, a jednak dzięki bibliotece Guzzle/Promise niemożliwe staje się możliwe, a PHP dzięki temu staje się asynchroniczny! Oczywiście to nie jest nic nowego, bo pierwsze wzmianki, które znalazłem są z 2015 roku, w którym pojawiła się wersja 0.1 tego zasobu.
Na stan dzisiejszy jest to dość mocno rozwinięty projekt, który cieszy się coraz większym poparciem. Ja o niej dowiedziałem się stosunkowo niedawno, co mi trochę wstyd za to, no ale lepiej później niż wcale, a świat bibliotek i dokumentacji jest ogromny 😄
Mam nadzieję, że pomogłem tym wpisem i że więcej PHP deweloperów z tego skorzysta.
Przytulam i trzymajcie się ciepło. Ciao 😊