JavaScript asíncrono para un rendimiento estable de la aplicación


Como desarrollador, naturalmente quiero que mi software sea confiable y receptivo. En los primeros días de mi carrera, los comentarios sobre mis solicitudes fueron mixtos. Algunas aplicaciones fueron muy elogiadas, pero las revisiones de otras aplicaciones fueron contradictorias porque dejaron de responder de forma intermitente durante la sesión, y todos sabemos lo impacientes que son los usuarios finales con la baja capacidad de respuesta del programa.

El problema subyacente era que las aplicaciones estaban codificadas con JavaScript puramente síncrono. Debido a que JavaScript ofrece (aparentemente) una funcionalidad asíncrona, es fácil pasar por alto que el tiempo de ejecución de JavaScript en sí mismo es síncrono de manera predeterminada, y esto es una trampa potencial para los desarrolladores. Mi curiosidad me llevó a investigar este enigma programático.

Índice
  1. El problema: bloqueo síncrono de JavaScript
  2. La solución: funcionalidad de JavaScript asíncrono
  3. Aplicaciones reales: un ejemplo de chatbot

El problema: bloqueo síncrono de JavaScript

Comencé mi exploración observando cómo funcionan las llamadas sincrónicas normales, centrándome en las pilas de llamadas: estructuras de programación de último en entrar, primero en salir (LIFO).

Todas las pilas de llamadas funcionan igual, independientemente del idioma: push (Agregar) llamadas de función a la pila y luego pop (quitarlos) según sea necesario.

Consideremos un ejemplo rápido:

function multiply(a, b) {
    return a * b;
}

function square(n) {
    return multiply(n, n);
}

function printSquare(n) {
    const squaredNum = square(n);
    console.log(squaredNum);
}

printSquare(4);

En nuestro ejemplo, la función exterior es printSquarellama al square función, que a su vez llama multiply. Las funciones se agregan a nuestra pila de llamadas en el orden en que aparecen. Cuando se completa cada método, se elimina del final de la pila de llamadas (es decir, multiply sería eliminado primero).

Una columna llamada pila de llamadas que contiene celdas etiquetadas (de abajo hacia arriba): imprimirCuadrado(4), cuadrado(4) y multiplicar(4, 4).
Ejemplo de pila de llamadas de JavaScript

Dado que la pila de llamadas es síncrona, si una o más de estas funciones tardan mucho tiempo, las tareas restantes se bloquearán. Nuestro programa deja de responder, al menos temporalmente, y no continuará hasta que se complete la función bloqueada.

Las llamadas de función comunes que causan estos retrasos en el programa son:

  • A while Bucle con una gran cantidad de iteraciones (por ejemplo, de una a un billón).
  • Una solicitud de red a un servidor web externo.
  • Un evento que espera que se complete un temporizador.
  • procesamiento de imágenes.

Para los usuarios finales en un navegador web, el bloqueo síncrono de llamadas hace que no puedan interactuar con los elementos de la página. Y para los desarrolladores, estas llamadas atascadas hacen que la consola de desarrollo sea inaccesible y los priva de la capacidad de examinar información detallada de depuración.

La solución: funcionalidad de JavaScript asíncrono

La codificación asíncrona es una técnica de programación en la que, después de llamar a una función, el resto de nuestro código puede ejecutarse sin esperar a que regrese la función original. Cuando se completa una tarea asincrónica, el tiempo de ejecución de JavaScript pasa el resultado a una función de nuestra elección. Este método elimina las barreras para nuestros usuarios finales y desarrolladores.

JavaScript implementa la funcionalidad asíncrona a través de algunos componentes arquitectónicos clave:

Una animación que muestra la interacción y el flujo entre la pila de llamadas de JavaScript, la API del navegador y la cola de tareas que admiten funciones asíncronas.
El flujo asíncrono de JavaScript

Cualquier cosa que deba ejecutarse de forma asincrónica (por ejemplo, un temporizador o una llamada API externa) se envía a la API del navegador (API web) del motor de tiempo de ejecución. La API del navegador genera un solo hilo de ejecución por operación reenviada.

Cada llamada de función de JavaScript asincrónica realizada a la API del navegador tiene una promesa correspondiente que se puede usar para activar el código del controlador cuando la función se completa (ya sea con éxito o sin éxito). Cuando la función se completa, independientemente de si devuelve un valor, su retorno cumple su promesa y la API del navegador mueve la función a la cola de tareas de JavaScript.

El jugador clave en el procesamiento asíncrono de JavaScript es su ciclo de eventos. El bucle de eventos verifica continuamente si la pila de llamadas y la cola de tareas están vacías y coordina cuándo mover esas llamadas asincrónicas completadas nuevamente a la pila de llamadas principal.

Ahora examinemos JavaScript setTimeout para ver el manejo de métodos asincrónicos de JavaScript en acción:

function a() {
    b();
}

function b() {
    setTimeout(() => {
        console.log("After 5 secs");
    }, 5000);
}

function c() {
    console.log("Hello World");
}

a();
c();
Una animación que muestra un flujo detallado desde la pila de llamadas de JavaScript a la API del navegador y la cola de tareas para el ejemplo de código anterior.
Cómo la API del navegador maneja eso setTimeoutfunción de

Repasemos el código:

  1. a va a la pila de llamadas.
  2. b'S setTimeout La llamada se inserta en la pila de llamadas de la API del navegador.
  3. c va a la pila de llamadas.
  4. c'S console.log La llamada se mueve a la pila de llamadas.
  5. Si eso setTimeout se completa, la API del navegador lo mueve a la cola de tareas.
  6. Todas las funciones dentro del proceso de pila de llamadas hasta su finalización.
  7. Cuando la pila de llamadas se vacía, el bucle de eventos empuja el setTimeoutde la cola de tareas de vuelta a la pila de llamadas.

Los ingenieros de software pueden ampliar sus habilidades de desarrollo aplicando estos métodos JavaScript asincrónicos. Ahora que hemos visto cómo se manejan los métodos asincrónicos dentro del tiempo de ejecución de JavaScript, demostraré su aplicabilidad con un ejemplo rápido.

Aplicaciones reales: un ejemplo de chatbot

Recientemente desarrollé un chatbot basado en navegador. El comportamiento sincrónico no habría sido deseable, ya que haría que la conversación pareciera inconexa y lenta. Mi solución logra una conversación de ritmo rápido a través de la comunicación asíncrona con el ChatGPT API externa para enviar y recibir mensajes.

Para facilitar la comunicación con el ChatGPT API Creé un servidor Node.js simple usando OpenAI. Luego usé el JavaScript asíncrono fetch API que utiliza promesas programáticas para proporcionar una forma de acceder y procesar respuestas:

  fetch('http://localhost:5000/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      query: 'What is the weather like in Seattle?'
    })
  })
  .then(response => response.json())
  .then(data => {
    console.log(data);
  });

Nuestro servidor simple llama asincrónicamente al ChatGPT Servicio mientras proporciona mensajería bidireccional.

Otro método asíncrono que uso con frecuencia es setInterval(). Esta función proporciona un temporizador incorporado que luego llama repetidamente a una función en cualquier intervalo especificado. Usar setIntervalAgregué un efecto de toque a la interfaz de usuario para que el usuario sepa que la otra parte (el chatbot) está creando una respuesta:

// Creating loader function for bot
function loader(element) {
    element.textContent = '';

    // 300 ms allows for real-time responsiveness indicating other-party typing
    loadInterval = setInterval(() => {
        element.textContent += '.';

        if (element.textContent === '....') {
            element.textContent = '';
        }
    }, 300);
}

// Creating typing functionality
function typeText(element, text) {
    let index = 0;
    // 20 ms allows for real-time responsiveness to mimic chat typing
    let interval = setInterval(() => {
        if (index < text.length) {
            element.innerHTML += text.charAt(index);
            index++;
        } else {
            clearInterval(interval);
        }
    }, 20);
}

Estos dos bloques asincrónicos convierten una conversación inconexa en una en la que los participantes se sienten involucrados. Pero la capacidad de respuesta que permite JavaScript asíncrono puede ser un factor clave menos obvio en otros contextos.

Más ejemplos de JavaScript asíncrono

Una vez me contrataron para crear un complemento personalizado de WordPress que permitiría a los usuarios cargar archivos grandes de forma asíncrona. Usé una biblioteca AJAX para permitir que el usuario cargue sus archivos en segundo plano sin esperar a que la página se actualice. Esto permitió una experiencia de usuario mucho más fluida y la aplicación fue un gran éxito.

En otro caso de uso, un sitio web de comercio electrónico tenía problemas con tiempos de carga lentos debido a la gran cantidad de imágenes que tenía que cargar. Para acelerar el proceso, implementé una función JavaScript asíncrona (LazyLoading) para cargar cada imagen de forma asíncrona. Esto permitió que el sitio web se cargara más rápido porque las imágenes no se cargaban todas al mismo tiempo.

También trabajé en un proyecto relacionado con una aplicación de transferencia de dinero que integra varias API de criptografía y pago. Necesitaba recuperar datos de una API externa, pero la API tardó un tiempo en responder. Para garantizar que la aplicación no se bloqueara mientras esperaba la API, implementé una función asíncrona que podía mantener la aplicación en ejecución mientras esperaba la respuesta de la API, lo que resultó en una experiencia de usuario mejorada.

Los métodos asincrónicos en una implementación de JavaScript permiten una potente funcionalidad para servir a los usuarios finales y reducir la ralentización o los bloqueos de la interfaz de usuario. Esta es la razón por la que JavaScript asíncrono es esencial para la participación del usuario en aplicaciones como Uber (que ejecuta sus procesos de reserva y pago en segundo plano), Twitter (carga los últimos tweets en tiempo real) y Dropbox (mantiene los archivos de los usuarios sincronizados y actualizados). hasta la fecha en todos los dispositivos) volverse) crucial).

Como desarrollador, es posible que le preocupe que los métodos de JavaScript asincrónicos no aparezcan en la pila de llamadas como se esperaba, pero tenga la seguridad de que sí. Puede agregar con confianza funcionalidad asíncrona a sus opciones para brindar experiencias de usuario superiores.

El Blog de Ingeniería de Toptal dice gracias Mohamed Asim Bilal por revisar el contenido técnico y los ejemplos de código presentados en este artículo.

Lectura adicional en el blog de ingeniería de Toptal:

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir