Klasa Promise w PHP i jej zastosowanie
Programowanie asynchroniczne zyskuje coraz większe znaczenie w nowoczesnym tworzeniu aplikacji, szczególnie tych pracujących w środowiskach serwerowych i obsługujących dużą liczbę żądań jednocześnie. W świecie PHP, który tradycyjnie był skoncentrowany na synchronicznym wykonywaniu kodu, asynchroniczność zaczęła odgrywać istotną rolę wraz z pojawieniem się nowych bibliotek i rozszerzeń. Jednym z kluczowych narzędzi w tym zakresie jest klasa Promise.
Co to jest Promise?
Klasa Promise to abstrakcja pozwalająca zarządzać operacjami asynchronicznymi w prosty i efektywny sposób. Podstawowym celem Promise jest przekazanie wyniku operacji asynchronicznej (sukcesu lub błędu) w przyszłości, gdy operacja ta zostanie zakończona.
Promise można wyobrazić sobie jako kontrakt, który może zostać spełniony (“zrealizowany”) lub odrzucony (“zawiedziony”). W PHP Promise umożliwiają efektywne zarządzanie kodem asynchronicznym, eliminując potrzebę skomplikowanych zagnieżdżeń funkcji zwrotnych.
Przykłady zastosowania klasy Promise
- Równoczesne żądania HTTP: W aplikacjach sieciowych można wykorzystać Promise do wykonywania wielu żądań HTTP jednocześnie, np. w celu pobrania danych z różnych API.
- Operacje na plikach: Promise pozwalają na asynchroniczne odczytywanie i zapisywanie plików bez blokowania głównego wątku aplikacji.
- Bazy danych: Możliwe jest wykonywanie równoległych zapytań do bazy danych.
- Procesy zewnętrzne: Promise nadają się do obsługi długotrwałych procesów zewnętrznych.
Implementacja Promise w PHP
W standardowej bibliotece PHP nie znajdziemy jeszcze natywnej klasy Promise, jednak istnieje kilka popularnych bibliotek, które dostarczają tę funkcjonalność, takich jak:
- Guzzle Promises: Rozszerzenie popularnej biblioteki HTTP Guzzle o obsługę Promise.
- ReactPHP: Framework do programowania asynchronicznego, który wspiera pracę z Promise.
- Amp: Narzędzie dedykowane asynchronicznym operacjom w PHP.
Przykład klasy Promise
class Promise
{
private $state = 'pending';
private $value = null;
private $onFulfilled = [];
private $onRejected = [];
public function __construct(callable $executor)
{
try {
$executor(
[$this, 'resolve'],
[$this, 'reject']
);
} catch (Exception $e) {
$this->reject($e);
}
}
public function then(callable $onFulfilled = null, callable $onRejected = null)
{
return new self(function ($resolve, $reject) use ($onFulfilled, $onRejected) {
$this->handle(
function ($value) use ($onFulfilled, $resolve, $reject) {
try {
$resolve($onFulfilled($value));
} catch (Exception $e) {
$reject($e);
}
},
function ($reason) use ($onRejected, $resolve, $reject) {
if ($onRejected) {
try {
$resolve($onRejected($reason));
} catch (Exception $e) {
$reject($e);
}
} else {
$reject($reason);
}
}
);
});
}
public function resolve($value)
{
if ($this->state !== 'pending') {
return;
}
$this->state = 'fulfilled';
$this->value = $value;
foreach ($this->onFulfilled as $callback) {
call_user_func($callback, $value);
}
}
public function reject($reason)
{
if ($this->state !== 'pending') {
return;
}
$this->state = 'rejected';
$this->value = $reason;
foreach ($this->onRejected as $callback) {
call_user_func($callback, $reason);
}
}
private function handle(callable $onFulfilled, callable $onRejected)
{
if ($this->state === 'fulfilled') {
call_user_func($onFulfilled, $this->value);
} elseif ($this->state === 'rejected') {
call_user_func($onRejected, $this->value);
} else {
$this->onFulfilled[] = $onFulfilled;
$this->onRejected[] = $onRejected;
}
}
public static function all(array $promises)
{
return new self(function ($resolve, $reject) use ($promises) {
$results = [];
$remaining = count($promises);
if ($remaining === 0) {
$resolve([]);
return;
}
foreach ($promises as $index => $promise) {
$promise->then(
function ($value) use (&$results, &$remaining, $index, $resolve) {
$results[$index] = $value;
$remaining--;
if ($remaining === 0) {
$resolve($results);
}
},
function ($reason) use ($reject) {
$reject($reason);
}
);
}
});
}
}
// Użycie klasy
$promise1 = new Promise(function ($resolve) {
$resolve("Promise 1 fulfilled");
});
$promise2 = new Promise(function ($resolve) {
$resolve("Promise 2 fulfilled");
});
Promise::all([$promise1, $promise2])->then(function ($results) {
print_r($results);
}, function ($error) {
echo "Error: $error";
});
$resolvedPromise = new Promise(function ($resolve, $reject) {
$resolve("Resolved successfully");
});
$resolvedPromise->then(function ($value) {
echo "Resolved value: $value\n";
});
$rejectedPromise = new Promise(function ($resolve, $reject) {
$reject("An error occurred");
});
$rejectedPromise->then(null, function ($reason) {
echo "Rejected reason: $reason\n";
});
Prosty przykład z wykorzystaniem Guzzle Promises
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
$client = new Client();
$promise1 = $client->getAsync('https://api.example.com/data1');
$promise2 = $client->getAsync('https://api.example.com/data2');
// Równoczesne uruchomienie obu żądań
$results = Promise\settle([$promise1, $promise2])->wait();
foreach ($results as $result) {
if ($result['state'] === 'fulfilled') {
echo $result['value']->getBody();
} else {
echo "Error: " . $result['reason'];
}
}
W powyższym kodzie równocześnie wykonywane są dwa żądania HTTP, a wyniki są obsługiwane po zakończeniu operacji.
Zalety korzystania z Promise
- Zwiększona wydajność: Możliwość wykonywania wielu operacji jednocześnie.
- Czytelny kod: Eliminacja zagnieżdżeń funkcji zwrotnych (tzw. callback hell).
- Obsługa błędów: Centralne zarządzanie błędami operacji asynchronicznych.
Podsumowanie
Klasa Promise stanowi potężne narzędzie umożliwiające efektywne zarządzanie operacjami asynchronicznymi w PHP. Choć natywna obsługa asynchroniczności nadal jest ograniczona, biblioteki takie jak Guzzle, ReactPHP czy Amp dostarczają potężnych mechanizmów dla programistów. Warto rozważyć ich wykorzystanie w projektach wymagających wysokiej wydajności i responsywności.