Introducción al paradigma de la programación asíncrona

1
127
Lenguajes asíncronos
Lenguajes asíncronos
GARD Pro Not Registered

Los lenguajes de programación asíncronos se basan en llamadas que pueden ser cumplidas ahora o en un futuro. Es decir, las variables pueden ser llenadas o asignadas en cualquier momento de la ejecución del programa.

Muchos de los lenguajes de programación populares se basan en procesos síncronos. Es decir, una orden solo se puede ejecutar luego que se ejecuta la anterior. Esto es muy beneficioso para el programador, debido a que no tiene que preocuparse de cuando los datos estarán listos. El problema de estos tipos de lenguajes es que los procesos ocupan más memoria, y son menos eficientes.

Ejemplos

Un ejemplo de este paradigma es PHP, un lenguaje síncrono. Cuando un usuario intenta acceder a un archivo de PHP por medio de un navegador web, un subproceso de Apache2 (el cual se encarga de manejar todas las llamadas a los archivos web y dar soporte a PHP) es llamado. El subproceso no se cierra hasta que el código termina de ejecutarse. Esto quiere decir que si tu archivo tarda un poco en ejecutarse y miles de personas intentan acceder al mismo archivo, se llega a un punto donde no se pueden abastecer a mas usuarios debido a la insuficiencia de recursos.

Otro ejemplo fácil de apreciar es el de un “botón”. Si cableamos un botón a un pin digital de un microcontrolador y lo intentamos leer, tenemos dos posibilidades:

  • Leerlo continuamente hasta que cambie de estado
  • Utilizar interrupciones

La primera es poco utilizada (a menos que realmente se necesite) debido a que es ineficiente. Utilizar cada ciclo de captura de datos y de analisis para conocer el estado del boton consume mucha más corriente que dejar el dispositivo en stand-by y utilizar interrupciones.

Se podría decir que las interrupciones en los microcontroladores son un ejemplo de llamadas asíncronas.  El problema de estas es que interrumpen la ejecución del código actual para llamar a la rutina de interrupción. Por esta razón, las rutinas deben ser lo más cortas posibles.

GARD Pro Not Registered

Ventajas y desventajas

Lenguajes síncronos

  • Fáciles de leer y entender
  • Fáciles de depurar.
  • No hay que preocuparse por la disponibilidad de los datos.

Lenguajes asíncronos

  • Su depuración no es tan sencilla. Un código puede que compile correctamente y puede que no funcione como lo pensamos. Puede que tengamos una función que depende de otra se ejecuta cuando no lo teníamos pensado.
  • Excelentes para el manejo de interfaces.
  • Se podría decir que son más dificiles de “entender”, debido a que es otro paradigma de programación.

Existen diversas infraestructuras (frameworks) que permiten a varios lenguajes (Python,Java,PHP, entre otros) convertirse en “lenguajes asíncronos”. El analísis de cada framework va mucho más alla de las metas de esta publicación.

Comparaciones entre los paradigmas

Para esto utilizaremos un lenguaje sencillo de entender como PHP para analizar los lenguajes síncronos, y NodeJS (este último es un framework de Javascript) para los lenguajes asíncronos.

Comparacion de codigos

Comparación de códigos. A la izquierda Javascript y a la derecha PHPAmbos códigos intentan resolver el mismo problema: mostrar una página web en donde se requiera cargar información del usuario. Es necesario cargar información de una base de datos (de cualquier cosa, ya sea productos, inventario, etc), cargar la cabecera de la página, el cuerpo y el pie de página y renderizar la misma. Se han obviado las comprobaciones de errores para hacer el código más corto y sencillo de entender.

PHP

Como podemos apreciar, PHP es mucho más fácil de leer. Sabemos que todas las funciones que estamos llamando estan dentro de un archivo de funciones llamado “funciones.php”, y que cada función se va a ejecutar justo después que se ejecute la anterior.

GARD Pro Not Registered

Javascript

En el ejemplo de Javascript, se utilizó una librería o módulo llamado “async”. Este permite agrupar funciones y hacer que el código sea más limpio. En este algoritmo se espera a que cada función termine de ejecutarse para iniciar la siguiente. La diferencia esta en que debido a la naturaleza asincrona de NodeJS (un framework de Javascript) debemos utilizar callbacks, las cuales son llamadas que se realizan cuando el código de una función termina de ejecutarse. Es una forma de hacerle saber al código o función principal que la función secundaria ha acabado de ejecutarse.

Es decir, en NodeJS no se puede hacer esto:

El código anterior compilaría correctamente, pero puede dar resultados no esperados. Es decir, el programa llama a la primera función e inmediatamente llama a la segunda, y así se va. Nunca espera a que la función anterior se termine de ejecutar. Entonces como la variable data es la que contiene la información de la base de datos que nos interesa, puede que no este lista cuando se llame a las funciones que dependan de esta ( en nuestro código sería cargarCuerpo() ). Esto podría ocasionar que el cuerpo de la página quede vacío.

Callbacks

Para solucionar el problema anterior se implementa el concepto de callbacks, para poder saber cuando una función termina de ejecutarse. Esto es muy útil, debido a es posible que la función que carga la base de datos tarde mucho. En otros lenguajes de programación esto consume recursos del sistema. Los lenguajes asincronos solo se consume los recursos por parte del servicio que utiliza la base de datos.

La sintaxis de un callback es sencilla, y una función normal puede ser transformada a una función asincrona. Para Javascript:

Callbacks
A la izquierda tenemos una función normal, y la derecha tenemos esa misma función con callback (asincrona).

Como podemos observar, cuando se utilizan callbacks se suele adoptar la notación de “Error primero” (o error first en ingles). Es decir, un callback deberá devolver dos valores:

Esto es una norma, y se hace para atrapar errores. Lo que usualmente se suele hacer es devolver nulo la variable err si no ha ocurrido ningún error, y lo contrario si ha ocurrido.

Promesas

Las promesas representan un valor o un objeto que puede estar disponible ahora o en cualquier momento futuro. Es decir, no se sabe en que momento puedan ser resueltas y ni cuando su valor de retorno estará disponible. El concepto es similar al de un callback, pero su sintaxis es un poco diferente.

Una promesa tiene dos formas de regresar al código original: si se completa o si ocurre un error. Esto es fácil de controlar utilizando los parámetros fullfill y reject. Éstos son son utilizados en el siguiente código:

Lo anterior se puede convertir una función que este basada en callbacks a una con promesas, de la siguiente forma:

Promesas
A la izquierda, la función del ejemplo anterior con callbacks. A la derecha, la misma función pero con promesas.

La ventaja de las promesas es que se pueden “combinar”. Es decir, si tenemos una función que cargue datos de la base de datos y necesitamos esos datos para procesarlos, podemos hacer algo como esto (asumiendo que la función cargarDatos() carga los datos de una base de datos, y que la función analizarDatos() los analiza de la manera que queremos) :

Y podemos seguir combinándola para utilizar más funciones

Este código anterior también se puede escribir de la siguiente forma (asumiendo que analizarDatos es una promesa):

Esto ahorra bastante espacio en comparación con los callbacks.

Estilos de ejecuciones asíncronas

  • Serie: Se ejecuta una promesa o una función asíncrona (basada en callbacks) después que se ejecuta la anterior.
  • Paralelo: Todos los procesos asíncronos (promesas o función asíncrona basada en callbacks) se ejecutan al mismo tiempo.
  • Paralelo con límite de ejecuciones paralelas: Todos los procesos asíncronos (promesas o función asíncrona basada en callbacks) se ejecutan al mismo tiempo, pero con un límite de procesos simultáneos.
  • Carrera (race): Todos los procesos asíncronos (promesas o función asíncrona basada en callbacks) se ejecutan al mismo tiempo, y cuando uno de estos se termina de ejecutar entonces se regresa al código original y se dejan de ejecutar los demás procesos asíncronos iniciados.

Esta información será útil para las siguientes publicaciones de Javascript y de interfaces web. Saludos

 

  • Antony García González