logo

Infierno de devolución de llamada en JavaScript

JavaScript es un lenguaje de programación asincrónico (sin bloqueo) y de un solo subproceso, lo que significa que solo se puede ejecutar un proceso a la vez.

En los lenguajes de programación, el infierno de devolución de llamadas generalmente se refiere a una forma ineficaz de escribir código con llamadas asincrónicas. También se la conoce como la Pirámide de la Perdición.

El infierno de devolución de llamada en JavaScript se conoce como una situación en la que se ejecuta una cantidad excesiva de funciones de devolución de llamada anidadas. Reduce la legibilidad y el mantenimiento del código. La situación del infierno de devolución de llamada generalmente ocurre cuando se trata de operaciones de solicitud asincrónicas, como realizar múltiples solicitudes de API o manejar eventos con dependencias complejas.

Para comprender mejor el infierno de las devoluciones de llamadas en JavaScript, primero comprenda las devoluciones de llamadas y los bucles de eventos en JavaScript.

Devoluciones de llamada en JavaScript

JavaScript considera todo como un objeto, como cadenas, matrices y funciones. Por tanto, el concepto de devolución de llamada nos permite pasar la función como argumento a otra función. La función de devolución de llamada completará la ejecución primero y la función principal se ejecutará más tarde.

Las funciones de devolución de llamada se ejecutan de forma asincrónica y permiten que el código continúe ejecutándose sin esperar a completar la tarea asincrónica. Cuando se combinan varias tareas asincrónicas y cada tarea depende de su tarea anterior, la estructura del código se vuelve complicada.

Entendamos el uso y la importancia de las devoluciones de llamada. Supongamos un ejemplo: tenemos una función que toma tres parámetros, una cadena y dos números. Queremos algún resultado basado en el texto de la cadena con múltiples condiciones.

Considere el siguiente ejemplo:

 function expectedResult(action, x, y){ if(action === 'add'){ return x+y }else if(action === 'subtract'){ return x-y } } console.log(expectedResult('add',20,10)) console.log(expectedResult('subtract',30,10)) 

Producción:

 30 20 

El código anterior funcionará bien, pero necesitamos agregar más tareas para que el código sea escalable. El número de declaraciones condicionales también seguirá aumentando, lo que dará lugar a una estructura de código desordenada que deberá optimizarse y ser legible.

Entonces, podemos reescribir el código de una mejor manera de la siguiente manera:

 function add(x,y){ return x+y } function subtract(x,y){ return x-y } function expectedResult(callBack, x, y){ return callBack(x,y) } console.log(expectedResult(add, 20, 10)) console.log(expectedResult(subtract, 30, 10)) 

Producción:

 30 20 

Aún así, el resultado será el mismo. Pero en el ejemplo anterior, definimos su cuerpo de función separado y pasamos la función como una función de devolución de llamada a la función resultado esperado. Por lo tanto, si queremos ampliar la funcionalidad de los resultados esperados para que podamos crear otro cuerpo funcional con una operación diferente y usarlo como función de devolución de llamada, será más fácil de entender y mejorará la legibilidad del código.

Hay otros ejemplos diferentes de devoluciones de llamada disponibles en funciones de JavaScript compatibles. Algunos ejemplos comunes son los detectores de eventos y funciones de matriz como mapear, reducir, filtrar, etc.

Para comprenderlo mejor, debemos comprender el paso por valor y el paso por referencia de JavaScript.

JavaScript admite dos tipos de datos: primitivos y no primitivos. Los tipos de datos primitivos son indefinidos, nulos, de cadena y booleanos, que no se pueden cambiar, o podemos decir comparativamente inmutables; Los tipos de datos no primitivos son matrices, funciones y objetos que se pueden cambiar o mutar.

Pasar por referencia pasa la dirección de referencia de una entidad, como si una función pudiera tomarse como argumento. Entonces, si se cambia el valor dentro de esa función, se alterará el valor original, que está disponible fuera de la función.

Comparativamente, el concepto de paso por valor no cambia su valor original, que está disponible fuera del cuerpo de la función. En cambio, copiará el valor en dos ubicaciones diferentes utilizando su memoria. JavaScript identificó todos los objetos por su referencia.

En JavaScript, addEventListener escucha eventos como clic, mouseover y mouseout y toma el segundo argumento como una función que se ejecutará una vez que se active el evento. Esta función se utiliza para pasar por concepto de referencia y se pasa usando sin paréntesis.

Considere el siguiente ejemplo; En este ejemplo, hemos pasado una función de bienvenida como argumento al addEventListener como función de devolución de llamada. Se invocará cuando se active el evento de clic:

Prueba.html:

 Javascript Callback Example <h3>Javascript Callback</h3> Click Here to Console const button = document.getElementById(&apos;btn&apos;); const greet=()=&gt;{ console.log(&apos;Hello, How are you?&apos;) } button.addEventListener(&apos;click&apos;, greet) 

Producción:

Infierno de devolución de llamada en JavaScript

En el ejemplo anterior, hemos pasado una función de bienvenida como argumento al addEventListener como función de devolución de llamada. Se invocará cuando se active el evento de clic.

De manera similar, el filtro también es un ejemplo de función de devolución de llamada. Si usamos un filtro para iterar una matriz, tomará otra función de devolución de llamada como argumento para procesar los datos de la matriz. Considere el siguiente ejemplo; En este ejemplo, estamos usando la función mayor para imprimir el número mayor que 5 en la matriz. Estamos utilizando la función isGreater como función de devolución de llamada en el método de filtro.

 const arr = [3,10,6,7] const isGreater = num =&gt; num &gt; 5 console.log(arr.filter(isGreater)) 

Producción:

Pete Davidson
 [ 10, 6, 7 ] 

El ejemplo anterior muestra que la función mayor se utiliza como función de devolución de llamada en el método de filtro.

Para comprender mejor las devoluciones de llamada y los bucles de eventos en JavaScript, analicemos JavaScript sincrónico y asincrónico:

JavaScript sincrónico

Entendamos cuáles son las características de un lenguaje de programación sincrónico. La programación síncrona tiene las siguientes características:

Ejecución de bloqueo: El lenguaje de programación síncrono admite la técnica de ejecución de bloqueo, lo que significa que bloquea la ejecución de declaraciones posteriores a las que se ejecutarán las declaraciones existentes. De esta manera, logra la ejecución predecible y determinista de las declaraciones.

Flujo secuencial: La programación sincrónica admite el flujo secuencial de ejecución, lo que significa que cada declaración se ejecuta de forma secuencial, como una tras otra. El programa de lenguaje espera a que se complete una declaración antes de pasar a la siguiente.

Sencillez: A menudo, la programación síncrona se considera fácil de entender porque podemos predecir el orden del flujo de ejecución. Generalmente, es lineal y fácil de predecir. Es bueno desarrollar aplicaciones pequeñas en estos lenguajes porque pueden manejar el orden crítico de operaciones.

Manejo directo de errores: En un lenguaje de programación síncrono, el manejo de errores es muy fácil. Si ocurre un error cuando se ejecuta una declaración, arrojará un error y el programa podrá detectarlo.

En pocas palabras, la programación sincrónica tiene dos características principales, es decir, se ejecuta una sola tarea a la vez y el siguiente conjunto de tareas siguientes solo se abordará una vez que finalice la tarea actual. De este modo sigue una ejecución de código secuencial.

Este comportamiento de la programación cuando se ejecuta una declaración, el lenguaje crea una situación de código de bloque ya que cada trabajo tiene que esperar a que se complete el trabajo anterior.

Pero cuando la gente habla de JavaScript, siempre ha sido una respuesta desconcertante si es sincrónico o asincrónico.

En los ejemplos analizados anteriormente, cuando usamos una función como devolución de llamada en la función de filtro, se ejecutó de forma sincrónica. Por eso se llama ejecución sincrónica. La función de filtro tiene que esperar a que la función mayor finalice su ejecución.

Por lo tanto, la función de devolución de llamada también se denomina bloqueo de devoluciones de llamada, ya que bloquea la ejecución de la función principal en la que se invocó.

Principalmente, JavaScript se considera de naturaleza síncrona y de bloqueo de un solo subproceso. Pero utilizando algunos enfoques, podemos hacer que funcione de forma asincrónica en función de diferentes escenarios.

Ahora, comprendamos el JavaScript asincrónico.

JavaScript asincrónico

El lenguaje de programación asincrónico se centra en mejorar el rendimiento de la aplicación. Las devoluciones de llamada se pueden utilizar en tales escenarios. Podemos analizar el comportamiento asincrónico de JavaScript con el siguiente ejemplo:

 function greet(){ console.log(&apos;greet after 1 second&apos;) } setTimeout(greet, 1000) 

En el ejemplo anterior, la función setTimeout toma una devolución de llamada y el tiempo en milisegundos como argumentos. La devolución de llamada se invoca después del tiempo mencionado (aquí 1). En pocas palabras, la función esperará 1s para su ejecución. Ahora, eche un vistazo al siguiente código:

 function greet(){ console.log(&apos;greet after 1 second&apos;) } setTimeout(greet, 1000) console.log(&apos;first&apos;) console.log(&apos;Second&apos;) 

Producción:

 first Second greet after 1 second 

Según el código anterior, los mensajes de registro después de setTimeout se ejecutarán primero mientras pasa el temporizador. Por lo tanto, resulta un segundo y luego el mensaje de saludo después de un intervalo de tiempo de 1 segundo.

En JavaScript, setTimeout es una función asincrónica. Siempre que llamamos a la función setTimeout, registra una función de devolución de llamada (saludo en este caso) que se ejecutará después del retraso especificado. Sin embargo, no bloquea la ejecución del código posterior.

En el ejemplo anterior, los mensajes de registro son declaraciones sincrónicas que se ejecutan inmediatamente. No dependen de la función setTimeout. Por lo tanto, ejecutan y registran sus respectivos mensajes en la consola sin esperar el retraso especificado en setTimeout.

Mientras tanto, el bucle de eventos en JavaScript maneja las tareas asincrónicas. En este caso, espera a que pase el retraso especificado (1 segundo) y, una vez transcurrido ese tiempo, selecciona la función de devolución de llamada (saludo) y la ejecuta.

Por lo tanto, el otro código después de la función setTimeout se ejecutaba mientras se ejecutaba en segundo plano. Este comportamiento permite que JavaScript realice otras tareas mientras espera que se complete la operación asincrónica.

Necesitamos comprender la pila de llamadas y la cola de devolución de llamadas para manejar los eventos asincrónicos en JavaScript.

Considere la siguiente imagen:

Infierno de devolución de llamada en JavaScript

En la imagen de arriba, un motor JavaScript típico consta de una memoria dinámica y una pila de llamadas. La pila de llamadas ejecuta todo el código sin esperar cuando se empuja a la pila.

La memoria del montón es responsable de asignar memoria para objetos y funciones en tiempo de ejecución cuando sean necesarios.

Ahora, nuestros motores de navegador constan de varias API web como DOM, setTimeout, console, fetch, etc., y el motor puede acceder a estas API utilizando el objeto de ventana global. En el siguiente paso, algunos bucles de eventos desempeñan el papel de guardián que selecciona las solicitudes de función dentro de la cola de devolución de llamada y las envía a la pila. Estas funciones, como setTimeout, requieren un cierto tiempo de espera.

Ahora, volvamos a nuestro ejemplo, la función setTimeout; cuando se encuentra la función, el temporizador se registra en la cola de devolución de llamada. Después de esto, el resto del código se inserta en la pila de llamadas y se ejecuta una vez que la función alcanza su límite de temporizador, caduca y la cola de devolución de llamada empuja la función de devolución de llamada, que tiene la lógica especificada y está registrada en la función de tiempo de espera. . Por lo tanto, se ejecutará después del tiempo especificado.

Escenarios del infierno de devolución de llamada

Ahora, hemos discutido las devoluciones de llamada, sincrónicas, asincrónicas y otros temas relevantes para el infierno de las devoluciones de llamadas. Entendamos qué es el infierno de devolución de llamada en JavaScript.

La situación en la que se anidan múltiples devoluciones de llamada se conoce como el infierno de las devoluciones de llamada, ya que la forma de su código parece una pirámide, que también se llama la 'pirámide de la perdición'.

El infierno de devolución de llamada hace que sea más difícil comprender y mantener el código. Podemos ver esta situación principalmente mientras trabajamos en el nodo JS. Por ejemplo, considere el siguiente ejemplo:

 getArticlesData(20, (articles) =&gt; { console.log(&apos;article lists&apos;, articles); getUserData(article.username, (name) =&gt; { console.log(name); getAddress(name, (item) =&gt; { console.log(item); //This goes on and on... } }) 

En el ejemplo anterior, getUserData toma un nombre de usuario que depende de la lista de artículos o debe extraerse de la respuesta getArticles que se encuentra dentro del artículo. getAddress también tiene una dependencia similar, que depende de la respuesta de getUserData. Esta situación se llama infierno de devolución de llamada.

El funcionamiento interno del infierno de devolución de llamada se puede entender con el siguiente ejemplo:

Entendamos que necesitamos realizar la tarea A. Para realizar una tarea, necesitamos algunos datos de la tarea B. De manera similar; Tenemos diferentes tareas que dependen unas de otras y se ejecutan de forma asincrónica. Por tanto, crea una serie de funciones de devolución de llamada.

Entendamos las Promesas en JavaScript y cómo crean operaciones asincrónicas, lo que nos permite evitar escribir devoluciones de llamadas anidadas.

Promesas de JavaScript

En JavaScript, las promesas se introdujeron en ES6. Es un objeto con un revestimiento sintáctico. Debido a su comportamiento asincrónico, es una forma alternativa de evitar escribir devoluciones de llamada para operaciones asincrónicas. Hoy en día, las API web como fetch() se implementan mediante promesa, que proporciona una forma eficiente de acceder a los datos desde el servidor. También mejoró la legibilidad del código y es una forma de evitar escribir devoluciones de llamadas anidadas.

Las promesas en la vida real expresan confianza entre dos o más personas y la seguridad de que algo en particular seguramente sucederá. En JavaScript, una Promesa es un objeto que garantiza la producción de un valor único en el futuro (cuando sea necesario). La promesa en JavaScript se utiliza para gestionar y abordar operaciones asincrónicas.

La Promesa devuelve un objeto que garantiza y representa la finalización o falla de las operaciones asincrónicas y su resultado. Es un proxy de un valor sin conocer el resultado exacto. Es útil para acciones asincrónicas para proporcionar un valor de éxito final o un motivo de fracaso. Por tanto, los métodos asincrónicos devuelven los valores como un método sincrónico.

Generalmente, las promesas tienen los siguientes tres estados:

  • Cumplido: el estado cumplido es cuando una acción aplicada se ha resuelto o completado con éxito.
  • Pendiente: el estado Pendiente es cuando la solicitud está en proceso y la acción aplicada no ha sido resuelta ni rechazada y aún se encuentra en su estado inicial.
  • Rechazado: El estado rechazado es cuando la acción aplicada ha sido rechazada, provocando que falle la operación deseada. La causa del rechazo puede ser cualquier cosa, incluida la caída del servidor.

La sintaxis de las promesas:

 let newPromise = new Promise(function(resolve, reject) { // asynchronous call is made //Resolve or reject the data }); 

A continuación se muestra un ejemplo de cómo escribir las promesas:

Este es un ejemplo de cómo escribir una promesa.

 function getArticleData(id) { return new Promise((resolve, reject) =&gt; { setTimeout(() =&gt; { console.log(&apos;Fetching data....&apos;); resolve({ id: id, name: &apos;derik&apos; }); }, 5000); }); } getArticleData(&apos;10&apos;).then(res=&gt; console.log(res)) 

En el ejemplo anterior, podemos ver cómo podemos usar eficientemente las promesas para realizar una solicitud desde el servidor. Podemos observar que la legibilidad del código aumenta en el código anterior que en las devoluciones de llamada. Las promesas proporcionan métodos como .then() y .catch(), que nos permiten manejar el estado de la operación en caso de éxito o fracaso. Podemos especificar los casos para los diferentes estados de las promesas.

Asíncrono/Espera en JavaScript

Es otra forma de evitar el uso de devoluciones de llamada anidadas. Async/Await nos permite utilizar las promesas de forma mucho más eficiente. Podemos evitar el uso del encadenamiento de métodos .then() o .catch(). Estos métodos también dependen de las funciones de devolución de llamada.

Async/Await se puede utilizar con precisión con Promise para mejorar el rendimiento de la aplicación. Resolvió internamente las promesas y proporcionó el resultado. Además, nuevamente, es más legible que los métodos () o catch().

No podemos usar Async/Await con las funciones de devolución de llamada normales. Para usarlo, debemos hacer que una función sea asincrónica escribiendo una palabra clave async antes de la palabra clave de función. Sin embargo, internamente también utiliza encadenamiento.

A continuación se muestra un ejemplo de Async/Await:

 async function displayData() { try { const articleData = await getArticle(10); const placeData = await getPlaces(article.name); const cityData = await getCity(place) console.log(city); } catch (err) { console.log(&apos;Error: &apos;, err.message); } } displayData(); 

Para utilizar Async/Await, la función debe especificarse con la palabra clave async y la palabra clave await debe escribirse dentro de la función. El async detendrá su ejecución hasta que la Promesa se resuelva o rechace. Se reanudará cuando se cumpla la Promesa. Una vez resuelta, el valor de la expresión de espera se almacenará en la variable que la contiene.

Resumen:

En pocas palabras, podemos evitar devoluciones de llamadas anidadas usando promesas y async/await. Aparte de estos, podemos seguir otros enfoques, como escribir comentarios y dividir el código en componentes separados también puede resultar útil. Pero, hoy en día, los desarrolladores prefieren el uso de async/await.

Conclusión:

El infierno de devolución de llamada en JavaScript se conoce como una situación en la que se ejecuta una cantidad excesiva de funciones de devolución de llamada anidadas. Reduce la legibilidad y el mantenimiento del código. La situación del infierno de devolución de llamada generalmente ocurre cuando se trata de operaciones de solicitud asincrónicas, como realizar múltiples solicitudes de API o manejar eventos con dependencias complejas.

Para comprender mejor el infierno de las devoluciones de llamadas en JavaScript.

método de subcadena en java

JavaScript considera todo como un objeto, como cadenas, matrices y funciones. Por tanto, el concepto de devolución de llamada nos permite pasar la función como argumento a otra función. La función de devolución de llamada completará la ejecución primero y la función principal se ejecutará más tarde.

Las funciones de devolución de llamada se ejecutan de forma asincrónica y permiten que el código continúe ejecutándose sin esperar a completar la tarea asincrónica.