
¿Qué es la asincronía? En resumen, asincronía significa realizar varias tareas durante un período específico de tiempo. PHP se ejecuta en un solo hilo, lo que significa que solo se puede ejecutar una pieza de código PHP en un momento dado. Esto puede parecer una limitación, pero en realidad nos da más libertad. Como resultado, no tenemos que enfrentar toda la complejidad asociada con la programación multiproceso. Pero, por otro lado, hay una serie de problemas. Tenemos que lidiar con la asincronía. Necesitamos de alguna manera administrarlo y coordinarlo.
Presentamos la traducción de un artículo del blog del desarrollador de backend de Skyeng Sergey Zhuk.
Por ejemplo, cuando ejecutamos dos solicitudes HTTP paralelas, decimos que se están "ejecutando en paralelo". Esto suele ser fácil y sencillo de hacer, pero surgen problemas cuando necesitamos simplificar las respuestas de estas solicitudes, por ejemplo, cuando una solicitud requiere datos recibidos de otra solicitud. Por lo tanto, es en la gestión de asincronía donde reside la mayor dificultad. Hay varias formas diferentes de resolver este problema.
Actualmente no hay soporte integrado para abstracciones de alto nivel para administrar la asincronía en PHP, y tenemos que usar bibliotecas de terceros como ReactPHP y Amp. En los ejemplos de este artículo, uso ReactPHP.
Promesas
Para comprender mejor la idea de las promesas, un ejemplo de la vida real será útil. Imagina que estás en McDonald's y quieres hacer un pedido. Usted paga dinero por ello y así comienza la transacción. En respuesta a esta transacción, espera obtener una hamburguesa y papas fritas. Pero el cajero no devuelve inmediatamente la comida. En su lugar, recibirá un cheque con el número de pedido. Considere este cheque como una promesa para un pedido futuro. Ahora puede tomar este cheque y comenzar a pensar en su delicioso almuerzo. La esperada hamburguesa y papas fritas aún no están listas, por lo que debe ponerse de pie y esperar hasta que se complete su pedido. Tan pronto como aparezca su número en la pantalla, cambiará el cheque de su pedido. Estas son las promesas:
Sustituir el valor futuro.
Una promesa es una representación de un significado futuro, una envoltura independiente del tiempo que envolvemos alrededor de un significado. No nos importa si el valor ya está aquí o todavía no. Seguimos pensando en él de la misma manera. Imagine que tenemos tres solicitudes HTTP asincrónicas que se ejecutan "en paralelo" para que se completen aproximadamente en un punto en el tiempo. Pero queremos coordinar y organizar de alguna manera sus respuestas. Por ejemplo, queremos imprimir estas respuestas tan pronto como se reciban, pero con una ligera restricción: no imprima la segunda respuesta hasta que se reciba la primera. Aquí quiero decir que si $ promise1 se cumple, entonces lo imprimimos. Pero si $ promise2 se cumple primero, no lo imprimimos, porque $ promise1 todavía está en progreso. Imagine que estamos tratando de adaptar tres solicitudes competitivas de tal manera que para el usuario final se vean como una solicitud rápida.
Entonces, ¿cómo podemos resolver este problema con promesas? En primer lugar, necesitamos una función que devuelva una promesa. Podemos recolectar tres de esas promesas y luego juntarlas. Aquí hay un código falso para esto:
<?php use React\Promise\Promise; function fakeResponse(string $url, callable $callback) { $callback("response for $url"); } function makeRequest(string $url) { return new Promise(function(callable $resolve) use ($url) { fakeResponse($url, $resolve); }); }
Aquí tengo dos funciones:
fakeResponse (string $ url, calllable $ callback) contiene una respuesta codificada y permite la devolución de llamada especificada con esta respuesta;
makeRequest (string $ url) devuelve una promesa que usa fakeResponse () para indicar que la solicitud se ha completado.
Desde el código del cliente, simplemente llamamos a la función makeRequest () y obtenemos las promesas:
<?php $promise1 = makeRequest('url1'); $promise2 = makeRequest('url2'); $promise3 = makeRequest('url3');
Era simple, pero ahora tenemos que ordenar estas respuestas de alguna manera. Una vez más, queremos que la respuesta de la segunda promesa se imprima solo después de completar la primera. Para resolver este problema, puede construir una cadena de promesas:
<?php $promise1 ->then('var_dump') ->then(function() use ($promise2) { return $promise2; }) ->then('var_dump') ->then(function () use ($promise3) { return $promise3; }) ->then('var_dump') ->then(function () { echo 'Complete'; });
En el código anterior, comenzamos con $ promise1 . Una vez que se completa, imprimimos su valor. No nos importa cuánto tiempo lleve: menos de un segundo o una hora. Tan pronto como se complete la promesa, imprimiremos su valor. Y luego esperamos $ promise2 . Y aquí podemos tener dos escenarios:
$ promise2 ya está completado e imprimimos inmediatamente su valor;
$ promise2 todavía se está cumpliendo y estamos esperando.
Gracias al encadenamiento de promesas, ya no tenemos que preocuparnos por si se ha cumplido alguna promesa. Promis no depende del tiempo y, por lo tanto, nos oculta sus estados (en el proceso, ya completados o cancelados).
Así es como puede controlar la asincronía con promesas. Y se ve muy bien, la cadena de promesas es mucho más bonita y más comprensible que un montón de devoluciones de llamadas anidadas.
Generadores
En PHP, los generadores son soporte de lenguaje incorporado para funciones que se pueden pausar y luego continuar. Cuando se detiene la ejecución de código dentro de dicho generador, parece un pequeño programa bloqueado. Pero fuera de este programa, fuera del generador, todo lo demás sigue funcionando. Esta es toda la magia y el poder de los generadores.
Literalmente, podemos pausar el generador localmente para esperar que se complete la promesa. La idea básica es usar promesas y generadores juntos. Asumen el control de la asincronía, y solo llamamos rendimiento cuando necesitamos suspender el generador. Aquí está el mismo programa, pero ahora estamos conectando generadores y promesas:
<?php use Recoil\React\ReactKernel;
Para este código, uso la biblioteca recoilphp / recoil , que le permite llamar a ReactKernel :: start () . Recoil hace posible el uso de generadores PHP para ejecutar promesas asincrónicas de ReactPHP.
Aquí, todavía estamos haciendo tres consultas en paralelo, pero ahora estamos ordenando las respuestas usando la palabra clave de rendimiento . Y nuevamente, mostramos los resultados al final de cada promesa, pero solo después de la anterior.
Corutinas
Las rutinas son una forma de dividir una operación o proceso en fragmentos, con cierta ejecución dentro de cada fragmento. Como resultado, resulta que en lugar de realizar toda la operación a la vez (lo que puede conducir a una congelación notable de la aplicación), se realizará gradualmente hasta que se complete toda la cantidad necesaria de trabajo.
Ahora que tenemos generadores renovables e interrumpibles, podemos usarlos para escribir código asincrónico con promesas en una forma sincrónica más familiar. Usando generadores y promesas PHP, puede deshacerse por completo de las devoluciones de llamada. La idea es que cuando hacemos una promesa (usando la llamada de rendimiento), una corutina se suscribe a ella. Corutin hace una pausa y espera hasta que se complete la promesa (completada o cancelada). Tan pronto como se complete la promesa, la rutina continuará cumpliéndose. Una vez finalizado con éxito, la promesa de rutina envía el valor recibido al contexto del generador mediante la llamada Generator :: send ($ value) . Si la promesa falla, entonces Corutin lanza una excepción a través del generador usando la llamada Generator :: throw () . En ausencia de devoluciones de llamada, podemos escribir código asíncrono que se parece casi al síncrono habitual.
Ejecución secuencial
Cuando se usa la rutina, el orden de ejecución en código asincrónico ahora importa. El código se ejecuta exactamente en el lugar donde se llama la palabra clave de rendimiento y luego se detiene hasta que se completa la promesa. Considere el siguiente código:
<?php use Recoil\React\ReactKernel;
Promise1: se mostrará aquí , luego la ejecución se detiene y espera. Tan pronto como se complete la promesa de makeRequest ('url1') , imprimimos su resultado y pasamos a la siguiente línea de código.
Manejo de errores
El estándar Promises / A + Promise establece que cada Promesa contiene los métodos then () y catch () . Esta interfaz le permite construir cadenas a partir de promesas y, opcionalmente, detectar errores. Considere el siguiente código:
<?php operation()->then(function ($result) { return anotherOperation($result); })->then(function ($result) { return yetAnotherOperation($result); })->then(function ($result) { echo $result; });
Aquí tenemos una cadena de promesas que pasa el resultado de cada promesa anterior a la siguiente. Pero no hay un bloque catch () en esta cadena, no hay manejo de errores aquí. Cuando falla una promesa en una cadena, la ejecución del código se mueve al controlador de errores más cercano en la cadena. En nuestro caso, esto significa que la promesa pendiente será ignorada, y cualquier error arrojado desaparecerá para siempre. Con las rutinas, el manejo de errores se destaca. Si falla alguna operación asincrónica, se generará una excepción:
<?php use Recoil\React\ReactKernel; use React\Promise\RejectedPromise;
Hacer que el código asincrónico sea legible
Los generadores tienen un efecto secundario realmente importante que podemos usar para controlar la asincronía y que resuelve el problema de la legibilidad del código asincrónico. Es difícil para nosotros entender cómo se ejecutará el código asincrónico debido al hecho de que el hilo de ejecución cambia constantemente entre diferentes partes del programa. Sin embargo, nuestro cerebro básicamente funciona sincrónicamente y de un solo subproceso. Por ejemplo, planificamos nuestro día de manera muy consistente: hacer uno, luego otro, y así sucesivamente. Pero el código asincrónico no funciona de la manera en que nuestros cerebros están acostumbrados a pensar. Incluso una simple cadena de promesas puede no parecer muy legible:
<?php $promise1 ->then('var_dump') ->then(function() use ($promise2) { return $promise2; }) ->then('var_dump') ->then(function () use ($promise3) { return $promise3; }) ->then('var_dump') ->then(function () { echo 'Complete'; });
Tenemos que desmontarlo mentalmente para comprender lo que está sucediendo allí. Entonces necesitamos un patrón diferente para controlar la asincronía. Y en resumen, los generadores proporcionan una forma de escribir código asincrónico para que parezca síncrono.
Las promesas y los generadores combinan lo mejor de ambos mundos: obtenemos código asíncrono con un gran rendimiento, pero al mismo tiempo parece síncrono, lineal y secuencial. Las rutinas le permiten ocultar la asincronía, que ya se está convirtiendo en un detalle de implementación. Y nuestro código al mismo tiempo parece que nuestro cerebro está acostumbrado a pensar, de forma lineal y secuencial.
Si estamos hablando de ReactPHP , entonces podemos usar la biblioteca RecoilPHP para escribir promesas en forma de rutina. En Amp, las corutinas están disponibles desde el primer momento.