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.
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 printSquare
llama 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).
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:
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();
Repasemos el código:
-
a
va a la pila de llamadas. -
b
'SsetTimeout
La llamada se inserta en la pila de llamadas de la API del navegador. -
c
va a la pila de llamadas. -
c
'Sconsole.log
La llamada se mueve a la pila de llamadas. - Si eso
setTimeout
se completa, la API del navegador lo mueve a la cola de tareas. - Todas las funciones dentro del proceso de pila de llamadas hasta su finalización.
- Cuando la pila de llamadas se vacía, el bucle de eventos empuja el
setTimeout
de 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 setInterval
Agregué 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.
Deja una respuesta