Promesas en PHP
Una promesa representa una operación asíncrona eventualmente cumplida o fallida. En este mismo sentido, una promesa podrá tener dos flujos principales los cuáles se ejecutarán mediante callbacks según se cumpla o no dicha promesa.
En PHP podemos utilizar la librería Guzzle Promises para trabajar con promesas utilizando el estándar Promises/A+.
Caraterísticas de las promesas
- Las promesas tienes tres estados, pendientes, cumplidas o fallidas.
- Las promesas se pueden encadenar de manera iterativa.
- Las promesas también se pueden resolver de manera síncrona.
Registro de callbacks
El método then()
permite definir el comportamiento que tendrá una promesa cuando se cumpla o cuando se rechace. Veamos el siguiente ejemplo:
use GuzzleHttp\Promise\Promise;
$promise = new Promise();
$promise->then(
// $onFulfilled
function ($value) {
echo 'The promise was fulfilled with value: ' . $value;
},
// $onRejected
function ($reason) {
echo 'The promise was rejected with reason: ' . $reason;
}
);
El método then()
recibe como primer parámetro un tipo callable
que será el comportamiento para la promesa cumplida y como segundo parámetro otro tipo callable
que que será el comportamiento para la promesa rechazada. Cuando se resuelve una promesa cumplida se provee un valor y cuando se rechaza se provee una razón.
Resolución de una promesa
Para resolver una promesa cumplida se debe utilizar el método resolve()
y proveer un valor resuelto. Tomando como ejemplo la promesa definida anteriormente obtendríamos el siguiente resultado al resolver la promesa.
$promise->resolve('some value');
// imprime 'The promise was fulfilled with value: some value'
Por el contrario para rechazar una promesa se debe utilizar el método reject()
y proveer una razón.
$promise->reject('some error');
// imprime 'The promise was rejected with reason: some error'
Encadenamiento de promesas
El método then()
siempre retorna una nueva promesa por ser resuelta. De acuerdo a esto es posible encadenar varias promesas y pasar el valor de una a otra hasta ejecutarlas todas. Veamos el siguiente ejemplo.
$promise = new Promise();
$promise
->then(
function ($value) {
return trim($value);
}
)->then(
function ($value) {
return '****' . $value . '****';
}
)->then(
function ($value) {
echo $value;
}
);
$promise->resolve(' hello ');
Al resolver esta promesa primero se limpiará el texto de espacios en blanco, posteriormente se le agregará el relleno (***texto***) y finalmente se imprimirá.
$promise->resolve(' hello ');
// imprime ****hello****
Siempre que se lance una excepción o se retorne un objeto GuzzleHttp\Promise\RejectedPromise
se resolverá la promesa mediante el callback $onRejected
, es decir, se rechazará la promesa con una razón. Veamos el siguiente ejemplo en el cuál se pretende dividir dos números y su resultado multiplicarlo por 100.
$promise = new Promise();
$promise
->then(
function ($value) {
$x = array_shift($value);
$y = array_shift($value);
if ($y === 0) {
throw new \Exception('Cannot divide by zero');
}
return $x / $y;
}
)->then(
function ($value) {
return $value * 100;
},
function ($reason) {
return 'Error ' . $reason->getMessage();
}
)->then(
function ($value) {
echo 'result: ' . $value;
},
function ($reason) {
echo 'There was an error on the error: ' . $reason;
}
);
Al resolver esta promesa con el valor de y=0 se ejecutará el callback $onRejected
. Después de esto se volverá a ejecutar el callback $onFulfilled
debido a que no hubo ningún error con el manejo de la anterior promesa. El resultado será el siguiente:
$promise->resolve(['x' => 5, 'y' => 0]);
// imprime result: Error Cannot divide by zero
Asincronismo
Por defecto las promesas de Guzzle funcionan de manera asíncrona. Esto significa que serán ejecutadas al final del script. Veamos el siguiente ejemplo:
$promise = new Promise();
$promise->then(
function ($value) {
echo 'The promise was fulfilled with value: ' . $value . PHP_EOL;
}
);
$promise->resolve('myValue');
echo 'Final ?';
Después de ejecutar este script de PHP obtendríamos el siguiente resultado:
Final ?
The promise was fulfilled with value: myValue
Si deseas que la promesa sea resuelta de manera síncrona debes pasar un callback al constructor de la clase Promise
y utilizar la función wait()
.
$promise = new Promise(function() use (&$promise) {
$promise->resolve('myValue');
});
$promise->then(
function ($value) {
echo 'The promise was fulfilled with value: ' . $value . PHP_EOL;
}
);
$promise->wait();
echo 'Final ?';
De esta forma el resultado será el esperado.
The promise was fulfilled with value: myValue
Final ?
Finalmente si deseas obtener algún valor calculado dentro del callback de las promesas deberás utilizar el paso por referencia de parámetros así:
$response = null;
$promise = new Promise(function() use (&$promise) {
$promise->resolve('myValue');
});
$promise->then(
function ($value) use (&$response) {
$response = 'The promise was fulfilled with value: ' . $value . PHP_EOL;
}
);
$promise->wait();
echo $response; // The promise was fulfilled with value: myValue