Devolución de llamadas de JavaScript, promesas y funciones asíncronas Parte 1

Introducción

Se habla mucho sobre programación asíncrona, pero ¿cuál es exactamente el problema? Lo importante es que queremos que nuestro código no sea bloqueado.. 

Las tareas que pueden bloquear nuestra aplicación incluyen realizar solicitudes HTTP, consultar una base de datos o abrir un archivo. Algunos lenguajes, como Java, lidian con esto creando múltiples hilos. Sin embargo, JavaScript solo tiene un hilo, por lo que necesitamos diseñar nuestros programas para que ninguna tarea esté bloqueando el flujo. 

La programación asíncrona resuelve este problema. Nos permite ejecutar tareas más adelante para no detener todo el programa a la espera de que se completen las tareas. También ayuda cuando queremos asegurar que las tareas se ejecuten secuencialmente. 

En la primera parte de este tutorial, aprenderemos los conceptos detrás del código síncrono y asíncrono y veremos cómo podemos usar las funciones de devolución de llamada para resolver problemas con la asincronía..

Contenido

  • Trapos
  • Síncrono vs Asíncrono
  • Funciones de devolución de llamada
  • Resumen
  • Recursos

Trapos

Quiero que recuerdes la última vez que fuiste de compras a la tienda de comestibles. Probablemente había varias cajas registradoras abiertas para revisar a los clientes. Esto ayuda a la tienda a procesar más transacciones en la misma cantidad de tiempo. Este es un ejemplo de concurrencia.. 

En pocas palabras, la concurrencia es hacer más de una tarea al mismo tiempo. Su sistema operativo es concurrente porque ejecuta múltiples procesos simultáneamente. Un proceso es un entorno de ejecución o una instancia de una aplicación en ejecución. Por ejemplo, su navegador, editor de texto y software antivirus son procesos en su computadora que se ejecutan simultáneamente.

Las aplicaciones también pueden ser concurrentes. Esto se consigue con hilos. No profundizaré demasiado porque esto va más allá del alcance de este artículo. Si desea una explicación detallada de cómo funciona JavaScript bajo el capó, le recomiendo ver este video. 

Un hilo es una unidad dentro de un proceso que está ejecutando código. En nuestro ejemplo de tienda, cada línea de pago sería un hilo. Si solo tenemos una línea de pago en la tienda, eso cambiaría la forma en que procesamos a los clientes. 

¿Alguna vez ha estado en línea y algo ha retrasado su transacción? Tal vez usted necesitaba una verificación de precios, o tuvo que ver a un gerente. Cuando estoy en la oficina de correos tratando de enviar un paquete y no tengo mis etiquetas llenas, el cajero me pide que se aleje mientras continúan revisando a otros clientes. Cuando estoy listo, vuelvo a la parte delantera de la línea para ser revisado. 

Esto es similar a cómo funciona la programación asíncrona. El cajero me pudo haber esperado. A veces lo hacen. Pero es una mejor experiencia no mantener la cola y revisar a los otros clientes. El punto es que los clientes no tienen que ser revisados ​​en el orden en que están en línea. Asimismo, el código no tiene que ejecutarse en el orden en que lo escribimos.. 

Síncrono vs Asíncrono

Es natural pensar que nuestro código se ejecuta de forma secuencial de arriba a abajo. Esto es síncrono. Sin embargo, con JavaScript, algunas tareas son inherentemente asíncronas (por ejemplo, setTimeout), y algunas tareas que diseñamos son asíncronas porque sabemos con anticipación que pueden estar bloqueando. 

Echemos un vistazo a un ejemplo práctico utilizando archivos en Node.js. Si desea probar los ejemplos de código y necesita una introducción sobre el uso de Node.js, puede encontrar las instrucciones de inicio en este tutorial. En este ejemplo, abriremos un archivo de publicaciones y recuperaremos una de las publicaciones. Luego abriremos un archivo de comentarios y recuperaremos los comentarios para esa publicación.. 

Esta es la forma sincrónica:

index.js

const fs = require ('fs'); const path = require ('path'); const postsUrl = path.join (__ dirname, 'db / posts.json'); const commentsUrl = path.join (__ dirname, 'db / comments.json'); // devuelve los datos de nuestra función de archivo loadCollection (url) try const response = fs.readFileSync (url, 'utf8'); devolver JSON.parse (respuesta);  catch (error) console.log (error);  // devolver un objeto por la función id getRecord (collection, id) return collection.find (function (element) return element.id == id;);  // devuelve una serie de comentarios para una función de publicación getCommentsByPost (comments, postId) return comments.filter (function (comment) return comment.postId == postId;);  // código de inicialización const posts = loadCollection (postsUrl); const post = getRecord (posts, "001"); const comments = loadCollection (commentsUrl); const postComments = getCommentsByPost (comments, post.id); console.log (post); console.log (postComments);

db / posts.json

["id": "001", "title": "Saludo", "text": "Hello World", "author": "Jane Doe", "id": "002", "title": "JavaScript 101", "texto": "Los fundamentos de la programación", "autor": "Alberta Williams", "id": "003", "título": "Programación asíncrona", "texto": " Callbacks, Promises and Async / Await. "," Autor ":" Alberta Williams "] 

db / comments.json

["id": "phx732", "postId": "003", "text": "No recibo estas cosas de devolución de llamada". , "id": "avj9438", "postId": "003", "text": "Esta es información realmente útil." , "id": "gnk368", "postId": "001", "text": "Este es un comentario de prueba." ]

los readFileSync Método abre el archivo de forma síncrona. Por lo tanto, también podemos escribir nuestro código de inicialización de manera sincrónica. Pero esta no es la mejor manera de abrir el archivo porque es una tarea potencialmente bloqueadora. La apertura del archivo se debe realizar de forma asíncrona para que el flujo de ejecución pueda ser continuo. 

Nodo tiene un leerarchivo Método que podemos utilizar para abrir el archivo de forma asíncrona. Esta es la sintaxis:

fs.readFile (url, 'utf8', función (error, datos) ...); 

Es posible que tengamos la tentación de devolver nuestros datos dentro de esta función de devolución de llamada, pero no estaremos disponibles para que los utilicemos dentro de nuestra loadCollection función. Nuestro código de inicialización también deberá cambiar porque no tendremos los valores correctos para asignar a nuestras variables. 

Para ilustrar el problema, veamos un ejemplo más simple. ¿Qué crees que imprimirá el siguiente código??

function task1 () setTimeout (function () console.log ('first');, 0);  function task2 () console.log ('second');  function task3 () console.log ('tercero');  tarea 1(); tarea 2(); task3 ();

Este ejemplo imprimirá "segundo", "tercero" y luego "primero". No importa que el setTimeout La función tiene un retardo de 0. Es una tarea asíncrona en JavaScript, por lo que siempre se aplazará para ejecutarse más tarde. los primera tarea La función puede representar cualquier tarea asíncrona, como abrir un archivo o consultar nuestra base de datos. 

Una solución para que nuestras tareas se ejecuten en el orden que deseamos es utilizar las funciones de devolución de llamada..

Funciones de devolución de llamada

Para usar las funciones de devolución de llamada, pasa una función como parámetro a otra función y luego la llama cuando su tarea ha finalizado. Si necesita una introducción sobre cómo usar las funciones de orden superior, reactivex tiene un tutorial interactivo que puede probar. 

Las devoluciones de llamada nos permiten forzar tareas para ejecutarse secuencialmente. También nos ayudan cuando tenemos tareas que dependen de los resultados de una tarea anterior. Usando devoluciones de llamada, podemos arreglar nuestro último ejemplo para que imprima "primero", "segundo" y luego "tercero".

función first (cb) setTimeout (function () return cb ('first');, 0);  función second (cb) return cb ('second');  función tercero (cb) return cb ('tercero');  primero (función (resultado1) console.log (resultado1); segundo (función (resultado2) console.log (resultado2); tercero (función (resultado3) console.log (resultado3););); );

En lugar de imprimir la cadena dentro de cada función, devolvemos el valor dentro de una devolución de llamada. Cuando nuestro código se ejecuta, imprimimos el valor que se pasó a nuestra devolución de llamada. Esto es lo que nuestro leerarchivo usos de la función. 

Volviendo a nuestros archivos de ejemplo, podemos cambiar nuestro loadCollection Función para que utilice devoluciones de llamada para leer el archivo de forma asíncrona..

function loadCollection (url, callback) fs.readFile (url, 'utf8', function (error, data) if (error) console.log (error); else return callback (JSON.parse (data)) ;); 

Y este es el aspecto que tendrá nuestro código de inicialización mediante el uso de devoluciones de llamada:

loadCollection (postsUrl, function (posts) loadCollection (commentsUrl, function (comments) getRecord (posts, "001", function (post) const postComments = getCommentsByPost (comments, post.id); console.log (post); console.log (postComments););););

Una cosa para notar en nuestra loadCollection La función es que en lugar de utilizar una trata de atraparlo declaración para manejar errores, utilizamos una si / else declaración. El bloque catch no podría capturar errores devueltos desde el leerarchivo llamar de vuelta. 

Es una buena práctica tener controladores de errores en nuestro código para los errores que son el resultado de influencias externas en lugar de errores de programación. Esto incluye acceder a archivos, conectarse a una base de datos o realizar una solicitud HTTP. 

En el ejemplo de código revisado, no incluí ningún manejo de errores. Si ocurriera un error en cualquiera de los pasos, el programa no continuará. Sería bueno proporcionar instrucciones significativas.

Un ejemplo de cuándo el manejo de errores es importante es si tenemos una tarea para iniciar sesión en un usuario. Esto implica obtener un nombre de usuario y una contraseña de un formulario, consultar nuestra base de datos para ver si es una combinación válida y luego redirigir al usuario a su panel de control si tenemos éxito. Si el nombre de usuario y la contraseña no fueran válidos, nuestra aplicación dejaría de ejecutarse si no le decimos qué hacer. 

Una mejor experiencia para el usuario sería devolver un mensaje de error al usuario y permitirle volver a intentar iniciar sesión. En nuestro ejemplo de archivo, podríamos pasar un objeto de error en la función de devolución de llamada. Este es el caso de la leerarchivo función. Luego, cuando ejecutamos el código, podemos agregar un si / else Declaración para manejar el resultado exitoso y el resultado rechazado..

Tarea

Usando el método de devolución de llamada, escriba un programa que abra un archivo de usuarios, seleccione un usuario y luego abra un archivo de publicaciones e imprima la información del usuario y todas sus publicaciones..

Resumen

La programación asíncrona es un método utilizado en nuestro código para aplazar eventos para su posterior ejecución. Cuando se trata de una tarea asíncrona, las devoluciones de llamada son una solución para sincronizar nuestras tareas para que se ejecuten de forma secuencial. 

Si tenemos varias tareas que dependen del resultado de las tareas anteriores, una solución es utilizar múltiples devoluciones de llamadas anidadas. Sin embargo, esto podría llevar a un problema conocido como "infierno de devolución de llamada". Las promesas resuelven el problema con el infierno de devolución de llamada, y las funciones asíncronas nos permiten escribir nuestro código de manera sincrónica. En la parte 2 de este tutorial, aprenderemos qué son y cómo usarlos en nuestro código..

Recursos

  • Philip Roberts: ¿Qué diablos es el evento Loop de todos modos?
  • Concurrencia en java
  • No sabes JavaScript: asíncrono y rendimiento
  • Node.js Event Loop
  • Bloqueo vs. No Bloqueo