Promesas en PHP

Author
Por Darío Rivera
Publicado el 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

Acerca de Darío Rivera

Author

Application Architect at Elentra Corp . Quality developer and passionate learner with 10+ years of experience in web technologies. Creator of EasyHttp , an standard way to consume HTTP Clients.

LinkedIn Twitter Instagram

Sólo aquellos que han alcanzado el éxito saben que siempre estuvo a un paso del momento en que pensaron renunciar.