¿Cómo funciona JavaScript?: Engine? Runtime? Call Stack?

JavaScript, es sin lugar a dudas, el lenguaje de programación del momento, el lenguaje de programación que arrasó en diferentes rankings del 2017. Se encuentra dentro del TOP 5 como lenguaje de programación con mayor crecimiento, comunidad, proyección, demanda laboral y popularidad; grandes compañías tecnológicas lo tienen incluido en su stack, aprovechando el poder y soporte de este lenguaje para sus proyectos en distintos niveles: front-end, back-end, aplicaciones híbridas, dispositivos integrados y mucho más.

JavaScript es el lenguaje de programación con mayor crecimiento, amplia comunidad de programadores, buena proyección, alta demanda laboral y gran popularidad del 2017.

No es extraño ver proyectos, publicaciones y un gran número de preguntas y respuestas sobre JavaScript en sitios web como Github, StackOverflow y Quora.

Fuente: https://madnight.github.io/githut/

Como se muestra en las estadísticas de GitHub, JavaScript está en la parte superior en términos de Repositorios activos y Total Pushes en GitHub. No se queda atrás en muchas otras categorías tampoco.

Seguro que muchos usan JavaScript a diario, pero ¿realmente sabemos como funciona JavaScript? ¿Qué hay detrás de JavaScript?

Quizás hallas oído o leído sobre:

El Motor V8 de Javascript“, “JavaScript tiene un solo subproceso” o “JavaScript usa una cola de callback“.

En esta publicación, revisaremos estos conceptos y explicaremos de manera breve y directa cómo se ejecuta JavaScript. Al conocer estos conceptos, podrás tener una base más sólida a la hora de programar aplicaciones y sobre todo a considerar la programación de aplicaciones Non-Blocking.

El Motor JavaScript (JavaScript Engine)

El motor Javascript, es el software que interpreta el código JavaScript y que a su vez, ejecuta un script acorde a las instrucciones dadas. Todos los navegadores web tienen un motor JavaScript.

Dentro de los motores JavaScript más conocidos tenemos:

V8 Engine, creado por Google, es open source y utilizado por el navegador Google Chrome. Además de funcionar en este famoso navegador, también lo han adaptado para correr de lado servidor, haciendo que JavaScript sea utilizado como lenguaje de programación back-end (Node JS).

Repositorio Oficial: https://github.com/v8/v8

Chakra, creado por Microsoft, es utilizado por su navegador web Internet Explorer 9 y en su nuevo navegador web Microsoft Edge (incluido en sus Sistemas Operativos Windows 10). En el 2015, liberaron el código fuente de su motor JScript convirtiéndolo en open source.

Repositorio Oficial: https://github.com/Microsoft/ChakraCore

SpiderMonkey, creado por la Fundación Mozilla, es utilizado por su navegador web Mozilla Firefox.

Repositorio Oficial: SpiderMonkey Project.

Carakan, creado por Opera Software, utilizado por su navegador Opera.

De todos los JavaScript Engine anteriormente mencionados, el que goza de mayor popularidad es el motor V8 de Google. El motor V8 se usa dentro de Chrome y Node JS. Una vista de alto nivel del motor V8, es la siguiente.

El motor se compone de dos componentes principales:

Memoria (Memory Heap), encargado de la asignación de memoria.

Pila de Llamadas a Funciones (Call Stack), aquí es donde se encuentran las funciones a medida que se ejecuta el código.

El Runtime (Tiempo de Ejecución)

De seguro, como desarrollador utilizas muy a menudo diferentes APIs, por ejemplo “setTimeout()“. Estas APIs, sin embargo no son proporcionadas por el motor V8. Entonces, ¿De donde vienen? con la siguiente imagen, nos daremos una idea.

Adicional al motor JavaScript (en este ejemplo: Motor Chrome V8), tenemos las “Web APIs” que son provistas por los navegadores web, como DOM, AJAX, setTimeout y mucho más.

Y luego, tenemos el popular bucle de eventos (Event Loop) y la cola de funciones (Queue Callback)


La Pila de Llamadas de Funciones (Call Stack)

JavaScript es un lenguaje de programación de un único subproceso, lo que significa que tiene una sola Pila de llamadas a funciones (Call Stack). Por lo tanto, puede hacer una cosa a la vez.

El Call Stack es una estructura de datos que registra básicamente en qué parte del programa estamos. Si entramos en una función, la colocamos en la parte superior de la pila. Si regresamos de una función, salimos de la parte superior de la pila. Eso es todo lo que la pila puede hacer.

Veamos un ejemplo.

Cuando el motor comienza a ejecutar este código, la Pila de llamadas de Funciones (Call Stack) estará vacía. Luego, los pasos serán los siguientes:

Cada entrada en la Pila de llamada de Funciones (Call Stack) se llama “Stack Frame“.

En el siguiente ejemplo, se muestra como se lanza una excepción.

Si ejecutamos este código en Google Chrome (suponiendo que este código se encuentre en un archivo llamado BooGame.js), se generará el siguiente stack trace:

Overflowing: Esto sucede cuando alcanzas el tamaño máximo del Call Stack. Y eso podría suceder con bastante facilidad, especialmente si está utilizando funciones de manera recursiva sin probar el código de forma exhaustiva. Por ejemplo:

Cuando el motor comienza a ejecutar este código, comienza llamando a la función “run()“. Esta función, sin embargo, es recursiva y comienza a llamarse a sí misma sin condiciones de terminación. Entonces, en cada paso de la ejecución, la misma función se agrega a la Pila (Call Stack) una y otra vez. Se ve algo como esto:

Sin embargo, en algún momento, el número de llamadas de función en la Call Stack excede su tamaño, y el navegador decide tomar medidas, lanzando un error, que puede verse más o menos así:

Ejecutar código en un único subproceso puede ser bastante fácil, ya que no tiene que lidiar con escenarios complicados que surgen en entornos de subprocesos múltiples. Pero correr en un solo hilo también es bastante limitante. Como JavaScript tiene una sola Call Stack, ¿Qué sucede cuando tienes  funciones en la Call Stack que toman una gran cantidad de tiempo para ser procesadas? ¿Esto nos impacta?, la verdad Si y mucho.

El problema es que mientras la Call Stack tiene funciones para ejecutar, el navegador no puede hacer otra cosa: se bloquea. Esto significa que el navegador no puede procesar, no puede ejecutar ningún otro código, simplemente está bloqueado. Y esto crea problemas si quieres buenas UI (User Interfaces) fluidas en tu aplicación.

La mayoría de los navegadores ante este escenario de bloqueo,  generan un mensaje de error, preguntándole si desea finalizar la página web. ¿Se te hace familiar la siguiente imagen?

Entonces, ¿cómo podemos ejecutar código pesado sin bloquear la interfaz de usuario y hacer que el navegador no responda?. La solución es utilizar asynchronous callbacks. En los enlaces de referencia, les comparto un artículo completo sobre este tema.

Fuentes de referencia:

Introducción al diseño de una API HTTP (Parte 4 de 4): Estándares

Este cuarto y último artículo hace un breve resumen sobre los distintos estándares aplicados en el diseño de una API HTTP para el intercambio de información.

Simple Response

Uno de los objetivos de nuestra API es responder con información que entienda el consumidor. Muchas veces, solo nos bastará con responder con solo el recurso solicitado o una matriz de recursos solicitados. Sin embargo, también hay metadatos que queremos proporcionar a nuestros consumidores. Una alternativa es  usar los HTTP Headers, los cuales son usados para proporcionar metadatos, pero por lo general no es lo suficientemente potente como para transmitir todo lo que el consumidor necesita saber del recurso.

Unos de los casos que se dan muy a menudo, son los mensajes de error que debemos proporcionar al consumidor de nuestra API. Si bien, podemos suministrar un código de estado 4XX o 5XX cuando falla una solicitud, pero ¿Cómo podemos ser más específicos? ¿Cómo hacemos para que nuestro cliente sepa si una solicitud es un error y cómo obtener mayor información del error?.

Ante este escenario, podemos responder con un objeto principal a los datos reales (en del HTTP Response). Nos referimos a responder con un objeto JSON estandarizado como un “sobre” (respuesta simple), ya que envuelve los datos importantes. Por ejemplo:

En el ejemplo, se observa dos propiedades de error. El primero, “error“, es un código de error analizable por una aplicación (consumidor). Hay APIs que optan por usar valores numéricos en vez de un valor de cadena, pero ¿por qué usar un formato numérico ilegible cuando lo tratan como una cadena? También queremos una cadena separada legible por humanos, como por ejemplo la propiedad “error_description“. Esta cadena podría traducirse teóricamente para coincidir con el Accept-Language del Request y mostrarle a un usuario final.

Si la respuesta (HTTP Response) fuera exitosa, podemos agregar propiedades adicionales como estas:

En este caso, todavía tenemos la propiedad de “error” y “error_description“, pero dado que no hay un error, lo configuramos en nulo (null). La propiedad “data” contiene el contenido solicitado por el consumidor, en este caso, el consumidor solicita una recopilación de datos (por ejemplo: estudiantes), por lo que proporcionamos una serie de recursos en esa colección (id y name). Finalmente tenemos dos propiedades de metadatos adicionales, “offset” y “per_page“, que le informa al consumidor sobre la respuesta. En este caso, el cliente ha solicitado la segunda página de resultados con 10 entradas por página, por lo que básicamente respondemos con esos datos para el contexto.

JSON API

API JSON es un estándar para el intercambio de información entre aplicaciones, que recomienda buenas prácticas para eliminar redundancias de datos, a la hora de devolver información a nuestros consumidores. Por citar un ejemplo, tenemos una API que representa a una colección de libros de una reconocida cadena de librerías. Cada libro tendrá un contenido único, es decir, tendrá un título, un código (identificador) y un precio. Sin embargo, cada libro tendrá información potencialmente redundante, como la información del autor.

Por lo general, ante estas situaciones, una API devuelve la información del autor de forma redundante por cada libro. Es probable que existan muchos libros cuyo autor es el mismo, por lo tanto, estaríamos enviando un montón de contenido al consumidor. El estándar API JSON nos permite, en cambio, definir relaciones entre diferentes tipos de recursos, eliminando así las redundancias. Mira el siguiente ejemplo:

GraphQL

GraphQL es un estándar API desarrollado por Facebook. Incluye un formato personalizado para consultar datos. Normalmente, las respuestas se envían en formato JSON. El formato de consulta requiere que el consumidor especifique todos los atributos que desea en la respuesta. Esto nació de la necesidad de que las aplicaciones clientes móviles obtengan solo datos importantes, desperdiciando menos bytes en la transferencia de información.

Otra característica de GraphQL es que los atributos solicitados en la respuesta pueden correlacionarse con datos de diferentes colecciones. Esto hace que GraphQL sea particularmente atractivo cuando se construyen componentes estructurales (facades), servicios que consumen datos de otros servicios. GraphQL puede realizar las agregaciones necesarias en una única solicitud (HTTP Request), evitando que el cliente tenga que realizar múltiples solicitudes a diferentes colecciones.

Las solicitudes generalmente usan un endpoint HTTP único, con el cuerpo recibido a través de POST. GraphQL NO es una práctica RESTful de HTTP, y en realidad se puede usar completamente por separado de HTTP. Este es un ejemplo de una consulta GraphQL:

Este es un ejemplo de la respuesta correlacionada (HTTP Response):

MessagePack

MessagePack se puede considerar como una representación binaria 1:1 de JSON. Cualquier documento JSON se puede representar como MessagePack, lo que significa que se puede usar con API JSON o GraphQL. Se elimina cualquier espacio en blanco superfluo y también se eliminan algunas otras redundancias, como los caracteres de comillas y dos puntos. La representación binaria suele ser más pequeña y puede ser más rápida de serializar y deserializar.

Observa el siguiente documento. Este archivo tiene 109 bytes (sin contar espacios en blanco). Es un objeto con tres propiedades, la primera es un identificador, la segunda una cadena y la tercera una matriz de dos valores numéricos:

El siguiente es el mismo contenido del archivo anterior, pero en formato MessagePack reducido a 78 bytes (72% del anterior).

JSON RPC (Remote Procedure Call)

JSON RPC es un paradigma muy diferente del HTTP RESTful que hemos estado viendo en todas estas publicaciones. En lugar de abstraer los datos en recursos y realizar operaciones CRUD en ellos, simplemente puede exponer las funciones y sus parámetros y permitir que los clientes llamen a estas funciones semi-directamente. Este patrón se llama RPC. JSON RPC es entonces

De forma similar a GraphQL, si usa estas solicitudes a través de HTTP, probablemente use un endpoint único, acepte las solicitudes a través de una solicitud POST y responda. JSON RPC también puede funcionar completamente fuera de HTTP, por ejemplo con TCP o IPC (Inter-Process Communications).

Aquí hay un ejemplo de una solicitud JSON RPC. El documento es muy simple; la solicitud requiere un número de versión (propiedad “jsonrpc“), un identificador (para correlacionar los request con los response, ya que no estamos casados con HTTP). También nombramos el método RPC (propiedad “method“) que queremos ejecutar y proporcionamos argumentos en la propiedad “params”. Los parametros pueden ser una matriz o un objeto, correlacionando a parámetros de función normal o parámetros nombrados, respectivamente.

La respuesta (HTTP Response) también contiene una versión y un identificador correspondiente a la solicitud (HTTP Request). La propiedad importante es “result” que contiene el resultado de la operación.

Artículos relacionados:

 

Fuentes: