Cómo escribir código asíncrono en Node.js

El autor seleccionó a Open Internet/Free Speech Fund para recibir una donación como parte del programa Write for DOnations.

Introducción

En muchos programas de JavaScript, el código se ejecuta tal como el desarrollador lo escribe: línea por línea. Esto se conoce como ejecución síncrona, porque las líneas se ejecutan una después de la otra, en el orden en que se escribieron. Sin embargo, no todas las instrucciones proporcionadas a la computadora deben aplicarse de inmediato. Por ejemplo, si envía una solicitud de red, el proceso de ejecución de su código deberá esperar que los datos se muestren para poder utilizarlos. En este caso, se desperdiciaría tiempo si no se ejecutara otro código al esperar que la solicitud de red se complete. Para solucionar este problema, los desarrolladores utilizan la programación asíncrona, a través de la cual las líneas de código se ejecutan en un orden diferente del que se utilizó para su escritura. Con la programación asíncrona, podemos ejecutar otro código mientras esperamos que finalicen actividades prolongadas como las solicitudes de red.

El código JavaScript se ejecuta en un solo hilo dentro de un proceso informático. Su código se procesa de forma sincrónica en este hilo y se ejecuta solo una instrucción a la vez. Por lo tanto, si hiciéramos una tarea de larga duración en este hilo, todo el código restante se bloquearía hasta que la tarea estuviera completa. Al aprovechar las funciones de programación asíncrona de JavaScript, podemos descargar las tareas de larga duración en un hilo en segundo plano para evitar este problema. Cuando se completa la tarea, el código que requerimos para procesar los datos de esta se vuelve a disponer en el hilo principal.

A través de este tutorial, verá que en JavaScript se administran las tareas asíncronas con ayuda del bucle de eventos, que es una construcción de JavaScript que completa una nueva tarea mientras se espera otra. Luego creará un programa en el que se utiliza la programación asíncrona para solicitar una lista de películas a una API de Studio Ghibli y guardar los datos en un archivo CSV. El código asíncrono se escribirá de tres maneras: devoluciones de llamadas, promesas y las palabras claves deasync y await.

Nota: A la fecha de la publicación de este artículo, en la programación asíncrona ya no se utiliza solo la devolución de llamadas, pero aprender este método obsoleto puede proporcionar una gran referencia respecto del motivo por el cual la comunidad de JavaScript ahora utiliza promesas. Las palabras claves async y await nos permiten usar promesas de una manera menos detallada y, por lo tanto, son la alternativa estándar para hacer programación asíncrona en JavaScript en el momento de la redacción de este artículo.

Requisitos previos

  • Node.js instalado en su equipo de desarrollo. En este tutorial, se utiliza la versión 10.17.0. Para instalarlo en macOS o Ubuntu 18.04, siga los pasos de Cómo instalar Node.js y crear un entorno de desarrollo local en macOS o las indicaciones de la sección Instalación con un PPA, de Cómo instalar Node.js en Ubuntu 18.04.
  • También tendrá que estar familiarizado con la instalación de paquetes en su proyecto. Póngase al día leyendo nuestra guía sobre Cómo usar los módulos Node.js con npm y package.json.
  • Es importante que se sienta cómodo creando y ejecutando funciones en JavaScript antes de aprender a usarlas de manera asíncrona. Si necesita una introducción o un repaso, puede leer nuestra guía Cómo definir funciones en JavaScript.

El bucle de eventos

Comencemos estudiando el funcionamiento interno de la ejecución de funciones de JavaScript. Comprender la forma en que esto se comporta le permitirá escribir código asíncrono de forma más deliberada y le servirá solucionar problemas relacionados con el código en el futuro.

A medida que en el intérprete de JavaScript se ejecuta el código, cada función invocada se añade a la pila de invocación de JavaScript. La pila de invocación es una pila: una estructura de datos similar a una lista cuyos elementos solo pueden añadirse y eliminarse al principio. En las pilas se sigue el principio “Último en entrar, primero en salir” o UEPS (LIFO). Si se añaden dos elementos en la pila, se eliminará primero el más reciente.

Lo ilustraremos con un ejemplo usando la pila de invocación. Si en JavaScript se encuentra una función functionA() invocada, se añadirá a la pila de invocación. Si en la función functionA() se invoca otra función functionB(), entonces functionB() se añade al principio de la pila de invocación. Cuando en JavaScript se completa la ejecución de una función, se elimina de la pila de invocación. Por lo tanto, JavaScript ejecutará primero functionB(), la eliminará de la pila cuando termine y luego completará la ejecución de functionA() y la eliminará de la pila de invocación. Por está razón, las funciones internas siempre se ejecutan antes que las externas.

Cuando en JavaScript se encuentra una operación asíncrona, como la de escribir en un archivo, se agrega a una tabla en la memoria. En esta tabla se almacena la operación, la condición para que se complete y la función que se invocará cuando se complete. A medida que la operación se completa, en JavaScript se añade la función asociada a la cola de mensajes. Una cola es otra estructura de datos en forma de lista en la que los elementos solo pueden añadirse al final y eliminarse al principio. En la cola de mensajes, si dos o más operaciones asíncronas están listas para que se ejecuten sus funciones, la función de la operación asíncrona que se complete primero se marcará para ejecutarse primero.

Las funciones se encuentran en espera para su adición a la pila de invocación en la cola de mensajes. El bucle de eventos es un proceso perpetuo en el que se verifica si la pila de invocación está vacía. Si es así, el primer elemento de la cola de mensajes se mueve a la pila de invocación. En JavaScript, se da prioridad a las funciones en la cola de mensajes respecto de la invocación de funciones que interpreta en el código. El efecto combinado de la pila de invocación, la cola de mensajes y el bucle de eventos permite procesar el código de JavaScript mientras se administran actividades asíncronas.

Ahora que comprende bien el bucle de eventos, sabe cómo se ejecutará el código asíncrono que escribió. Con este conocimiento, ahora podrá crear código asíncrono usando tres enfoques diferentes: devolución de llamadas, promesas, y async y await.

Programación asíncrona con devolución de llamadas

Una función de devolución de llamada es aquella que se pasa como argumento a otra función y luego se ejecuta cuando la otra función se completa. Usamos devoluciones de llamadas para garantizar que el código se ejecute solo después de que se completa una operación asíncrona.

Durante mucho tiempo, las devoluciones de llamadas fueron el mecanismo más común para escribir código asíncrono, pero ahora se volvieron obsoletas en gran parte porque pueden dificultar la lectura del código. En este paso, escribirá un código asíncrono de ejemplo usando las devoluciones de llamadas a fin de poder utilizarlo como base para ver el aumento de la eficiencia en comparación con otras estrategias.

Existen muchas formas de usar las funciones de devolución de llamadas en otra función. Generalmente, se utiliza la siguiente estructura:

function asynchronousFunction([ Function Arguments ], [ Callback Function ]) {     [ Action ] } 

Aunque no es requisito sintáctico de JavaScript ni de Node.js que la función de devolución de llamada sea el último argumento de la función externa, es una práctica común que facilita la identificación de las devoluciones de llamadas. También es común que los desarrolladores de JavaScript utilicen una función anónima como devolución de llamada. Las funciones anónimas son aquellas que se crean sin nombre. Una función suele ser mucho más legible cuando se define al final de la lista de argumentos.

Para demostrar las devoluciones de llamadas, crearemos un módulo Node.js que se escribe una lista de películas de Studio Ghibli en un archivo. Primero, cree una carpeta en la que se almacenará nnuestro archivo de JavaScript y su resultado:

  • mkdir ghibliMovies

Luego acceda a esa carpeta:

  • cd ghibliMovies

Comenzaremos realizando una solicitud HTTP al API de Studio Ghibli, cuyos resultados se registrarán a través de nuestra función de devolución de llamadas. Para hacer esto, instalaremos una biblioteca que nos permita acceder a los datos de una respuesta HTTP en una devolución de llamada.

En su terminal, inicie npm para que podamos tener una referencia para nuestros paquetes más adelante:

  • npm init -y

A continuación, instale la biblioteca request:

  • npm i request --save

Ahora, abra un nuevo archivo llamado callbackMovies.js en un editor de texto como nano:

  • nano callbackMovies.js

En su editor de texto, introduzca el código siguiente. Comenzaremos enviando una solicitud HTTP con el módulo request:

callbackMovies.js

const request = require('request');  request('https://ghibliapi.herokuapp.com/films'); 

En la primera línea, cargaremos el módulo request que se instaló a través de npm. En el módulo se muestra una función en la que se pueden hacer solicitudes HTTP; luego guardaremos esa función en la constante request.

Después, hacemos la solicitud HTTP usando la función request(). Ahora, imprimiremos los datos de la solicitud HTTP en la consola añadiendo los siguientes cambios resaltados:

callbackMovies.js

const request = require('request');  request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {     if (error) {         console.error(`Could not send request to API: ${error.message}`);         return;     }      if (response.statusCode != 200) {         console.error(`Expected status code 200 but received ${response.statusCode}.`);         return;     }      console.log('Processing our list of movies');     movies = JSON.parse(body);     movies.forEach(movie => {         console.log(`${movie['title']}, ${movie['release_date']}`);     }); }); 

Cuando usamos la función request(), le asignamos dos parámetros:

  • la URL del sitio web que intentamos solicitar;
  • una función de devolución de llamada que se ocupa de cualquier error o respuesta exitosa una vez que se completa la solicitud.

En nuestra función de devolución de llamada existen tres argumentos: error, response y body. Cuando la solicitud HTTP se completa, se otorgan automáticamente valores a los argumentos dependiendo del resultado. Si la solicitud no se enviara, error contendría un objeto. Sin embargo, response y body tendrían el valor null. Si la solicitud se realizó correctamente, la respuesta HTTP se almacenará en response. Si en nuestra respuesta HTTP se muestran datos (en este ejemplo obtenemos JSON), los datos se establecen en body.

En nuestra función de devolución de llamada primero se verifica si se muestra un error. Es recomendable verificar primero si hay errores en una devolución de llamada para que la ejecución de esta no continúe con datos faltantes. En este caso, registramos el error y la ejecución de la función. Luego, verificamos el código de estado de la respuesta. Es posible que nuestro servidor no esté siempre disponible. A su vez, las API pueden cambiar. Esto hará que las solicitudes que una vez fueron razonables pasen a ser incorrectas. Al verificar que el código de estado sea 200, lo cual significa que la solicitud fue “OK”, podemos tener la confianza de que nuestra respuesta sea lo que esperábamos.

Por último, analizamos el cuerpo de respuesta de un Array e iteramos cada película para registrar su nombre y año de estreno.

Después de guardar y cerrar el archivo, ejecute esta secuencia de comandos con lo siguiente:

  • node callbackMovies.js

Verá el siguiente resultado:

OutputCastle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014  

Obtuvimos con éxito una lista de películas de Studio Ghibli con el año en que se estrenaron. Ahora, terminaremos este programa escribiendo la lista de películas que actualmente registramos en un archivo.

Actualice el archivo callbackMovies.js en su editor de texto para incluir el siguiente código resaltado, que crea un archivo CSV con los datos de nuestras películas:

callbackMovies.js

const request = require('request'); const fs = require('fs');  request('https://ghibliapi.herokuapp.com/films', (error, response, body) => {     if (error) {         console.error(`Could not send request to API: ${error.message}`);         return;     }      if (response.statusCode != 200) {         console.error(`Expected status code 200 but received ${response.statusCode}.`);         return;     }      console.log('Processing our list of movies');     movies = JSON.parse(body);     let movieList = '';     movies.forEach(movie => {         movieList += `${movie['title']}, ${movie['release_date']}n`;     });      fs.writeFile('callbackMovies.csv', movieList, (error) => {         if (error) {             console.error(`Could not save the Ghibli movies to a file: ${error}`);             return;         }          console.log('Saved our list of movies to callbackMovies.csv');;     }); }); 

Observando los cambios resaltados, podemos ver que importamos el módulo fs. Este módulo forma parte de la configuración estándar de todas las instalaciones de Node.js y contiene un método writeFile() que puede escribir en un archivo de forma asíncrona.

En lugar de registrar los datos en la consola, ahora los añadiremos a una variable de cadena movieList. Luego, usaremos writeFile() para guardar el contenido de movieList en un archivo nuevo: callbackMovies.csv. Por último, proporcionaremos una devolución de llamada a la función writeFile(), que tiene un argumento: error. Esto nos permite manejar los casos en los que no podemos escribir en un archivo; por ejemplo, cuando el usuario en el que se ejecuta el proceso node no tiene esos permisos.

Guarde el archivo y ejecute este programa de Node.js una vez más con lo siguiente:

  • node callbackMovies.js

En su carpeta ghibliMovies, visualizará callbackMovies.csv, que tiene el siguiente contenido:

callbackMovies.csv

Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014 

Es importante observar que escribimos en nuestro archivo CSV en la devolución de llamada de la solicitud HTTP. Una vez que el código se encuentra en la función de devolución de llamada, solo escribirá en el archivo después de que se haya completado la solicitud HTTP. Si quisiéramos comunicarnos con una base de datos después de escribir nuestro archivo CSV, crearíamos otra función asíncrona que se invocaría en la devolución de llamada de nuestro writeFile(). Mientras más código asíncrono tengamos, más funciones de devolución de llamada deberán anidarse.

Imaginemos que queremos ejecutar cinco operaciones asíncronas, cada una de las cuales solo se puede ejecutar cuando otra se complete. Si codificáramos esto, tendríamos algo como lo siguiente:

doSomething1(() => {     doSomething2(() => {         doSomething3(() => {             doSomething4(() => {                 doSomething5(() => {                     // final action                 });             });         });     }); }); 

Cuando en las devoluciones de llamada anidadas hay muchas líneas de código para ejecutar, se vuelven considerablemente más complejas e ilegibles. A medida que aumenten el tamaño y la complejidad de su proyecto de JavaScript, este efecto se hará más pronunciado hasta que finalmente no pueda manejarse. Debido a esto, los desarrolladores ya no utilizan las devoluciones de llamada para manejar las operaciones asíncronas. Para mejorar la sintaxis de nuestro código asíncrono, podemos usar promesas como alternativa.

Usar promesas para la programación asíncrona concisa

Una promesa es un objeto de JavaScript en el que se mostrará un valor en algún momento del futuro. En las funciones asíncronas se pueden mostrar objetos de promesas en lugar de valores concretos. Si obtenemos un valor en el futuro, afirmamos que la promesa se cumplió. Si obtenemos un error en el futuro, afirmamos que la promesa se rechazó. De lo contrario, se siguen realizando tareas vinculadas a la promesa en un estado de operación pendiente.

Las promesas suelen adoptar la siguiente forma:

promiseFunction()     .then([ Callback Function for Fulfilled Promise ])     .catch([ Callback Function for Rejected Promise ]) 

Como se muestra en esta plantilla, en las promesas también se utilizan las funciones de devolución de llamada. Tenemos una función de devolución de llamada para el método then(), que se ejecuta cuando se cumple una promesa. También tenemos una función de devolución de llamada para que en el método catch() se pueda manejar cualquier error que se produzca mientras la promesa está en ejecución.

Obtengamos experiencia de primera mano con las promesas reescribiendo nuestro programa de Studio Ghibli para usar estas en su lugar.

Axios es un cliente HTTP basado en promesas para JavaScript; lo instalaremos:

  • npm i axios --save

Ahora, con su editor de texto preferido, cree un archivo nuevo promiseMovies.js:

  • nano promiseMovies.js

En nuestro programa, se realizará una solicitud HTTP con axios y luego se utilizará una versión especial de fs basada en promesas para guardar el guardado en un nuevo archivo CSV.

Escriba este código en promiseMovies.js para que podamos cargar Axios y enviar una solicitud HTTP a la API de películas:

promiseMovies.js

const axios = require('axios');  axios.get('https://ghibliapi.herokuapp.com/films'); 

En la primera línea, cargamos el módulo axios y almacenamos la función mostrada en una constante llamada axios. Luego, usamos el método axios.get() para enviar una solicitud HTTP a la API.

Con el método axios.get() se obtiene una promesa. Encadenaremos esa promesa para poder imprimir la lista de películas de Ghibli en la consola:

promiseMovies.js

const axios = require('axios'); const fs = require('fs').promises;   axios.get('https://ghibliapi.herokuapp.com/films')     .then((response) => {         console.log('Successfully retrieved our list of movies');         response.data.forEach(movie => {             console.log(`${movie['title']}, ${movie['release_date']}`);         });     }) 

Desglosaremos lo que está sucediendo. Después de realizar una solicitud HTTP GET con axios.get(), usamos la función then(), que se ejecuta solo cuando la promesa se cumple. En este caso, imprimimos las películas en la pantalla al igual que en el ejemplo de devoluciones de llamadas.

Para mejorar este programa, agregue el código resaltado para escribir los datos HTTP en un archivo:

promiseMovies.js

const axios = require('axios'); const fs = require('fs').promises;   axios.get('https://ghibliapi.herokuapp.com/films')     .then((response) => {         console.log('Successfully retrieved our list of movies');         let movieList = '';         response.data.forEach(movie => {             movieList += `${movie['title']}, ${movie['release_date']}n`;         });          return fs.writeFile('promiseMovies.csv', movieList);     })     .then(() => {         console.log('Saved our list of movies to promiseMovies.csv');     }) 

Además, importaremos el módulo fs una vez más. Observe que después de la importación fs obtenemos .promises. Node.js incluye una versión basada en promesas de la biblioteca fs basada en devoluciones de llamadas, por lo que la compatibilidad con versiones anteriores se mantiene en los proyectos heredados.

La primera función then(), en la que se procesa la solicitud HTTP, ahora invoca fs.writeFile() en lugar de realizar impresiones en la consola. Debido a que importamos la versión basada en promesas de fs, nuestra función writeFile() muestra otra promesa. Por lo tanto, añadimos otra función then() para cuando se cumpla la promesa de writeFile().

Una promesa puede mostrar una nueva promesa, lo que nos permite ejecutarlas una después de la otra. Esto nos permite realizar varias operaciones asíncronas. Esto se conoce como encadenamiento de promesas y es análogo a la anidación de devoluciones de llamada. El segundo then() solo se invoca una vez que se escribe con éxito en el archivo.

Nota: En este ejemplo, no revisamos el código de estado HTTP como en el ejemplo de devolución de llamada. Por defecto, en axios no se cumple la promesa si se obtiene un código de estado que indica un error. Por lo tanto, ya no necesitamos validarlo.

Para completar este programa, encadene la promesa con una función catch(), como se resalta en lo siguiente:

promiseMovies.js

const axios = require('axios'); const fs = require('fs').promises;   axios.get('https://ghibliapi.herokuapp.com/films')     .then((response) => {         console.log('Successfully retrieved our list of movies');         let movieList = '';         response.data.forEach(movie => {             movieList += `${movie['title']}, ${movie['release_date']}n`;         });          return fs.writeFile('promiseMovies.csv', movieList);     })     .then(() => {         console.log('Saved our list of movies to promiseMovies.csv');     })     .catch((error) => {         console.error(`Could not save the Ghibli movies to a file: ${error}`);     }); 

Si alguna promesa no se cumple en la cadena de promesas, JavaScript se dirige automáticamente a la función catch() si es que se definió. Es por eso que solo tenemos una cláusula catch() aunque tengamos dos operaciones asíncronas.

Confirmaremos que nuestro programa produce el mismo resultado ejecutando lo siguiente:

  • node promiseMovies.js

En su carpeta ghibliMovies, verá el archivo promiseMovies.csv que contiene lo siguiente:

promiseMovies.csv

Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014 

Al usar promesas, podemos escribir código mucho más conciso que al emplear únicamente devoluciones de llamadas. La cadena de promesas de devoluciones de llamadas es una opción más ordenada que la anidación de devoluciones de llamadas. Sin embargo, a medida que realicemos más invocaciones asíncronas, nuestra cadena de promesas se vuelve más larga y difícil de mantener.

El nivel de detalle de las devoluciones de llamada y las promesas se debe a la necesidad de crear funciones cuando tenemos el resultado de una tarea asíncrona. Una mejor idea sería esperar un resultado asíncrono y disponerlo en una variable fuera de la función. De esta manera, podemos usar los resultados de las variables sin tener que crear una función. Podemos lograr esto con las palabras claves async y await.

Escribir JavaScript con async y await

Las palabras claves async y await proporcionan una sintaxis alternativa al trabajar con promesas. En lugar de tener el resultado de una promesa disponible en el método then(), el resultado se devuelve como un valor igual que en cualquier otra función. Definiremos una función con la palabra clave async para que se indique en JavaScript que es una función asíncrona que muestra una promesa. Usamos la palabra clave await para indicar en JavaScript que se muestren los resultados de la promesa en lugar de la propia promesa cuando se cumpla.

En general, cuando se usan async y await el panorama es similar al siguiente:

async function() {     await [Asynchronous Action] } 

Veamos la manera en que async y await pueden mejorar nuestro programa de Studio Ghibli. Utilice su editor de texto para crear y abrir un archivo nuevo syncAwaitMovies.js:

  • nano asyncAwaitMovies.js

En su archivo de JavaScript recién abierto, empezaremos por importar los mismos módulos que usamos en nuestro ejemplo de promesas:

asyncAwaitMovies.js

const axios = require('axios'); const fs = require('fs').promises; 

Las importaciones son iguales que promiseMovies.js porque en async y await se utilizan promesas.

Ahora, usaremos la palabra clave async para crear una función con nuestro código asíncrono:

asyncAwaitMovies.js

const axios = require('axios'); const fs = require('fs').promises;  async function saveMovies() {} 

Crearemos una nueva función llamada saveMovies() pero incluiremos async al principio de su definición. Esto es importante, ya que solo podemos usar la palabra clave await en una función asíncrona.

Utilice la palabra clave await para realizar una solicitud HTTP en la que se obtenga la lista de películas de la API de Ghibli:

asyncAwaitMovies.js

const axios = require('axios'); const fs = require('fs').promises;  async function saveMovies() {     let response = await axios.get('https://ghibliapi.herokuapp.com/films');     let movieList = '';     response.data.forEach(movie => {         movieList += `${movie['title']}, ${movie['release_date']}n`;     }); } 

En nuestra función saveMovies(), realizaremos una solicitud HTTP con axios.get() como antes. Esta vez, no la encadenamos con una función then(). En su lugar, añadiremos await antes de su invocación. Cuando JavaScript detecte await, solo ejecutará el código restante de la función después que axios.get() finalice la ejecución y configure la variable response. En el otro código se guardan los datos de las películas para que podamos escribir en un archivo.

Escribiremos los datos de las películas en un archivo:

asyncAwaitMovies.js

const axios = require('axios'); const fs = require('fs').promises;  async function saveMovies() {     let response = await axios.get('https://ghibliapi.herokuapp.com/films');     let movieList = '';     response.data.forEach(movie => {         movieList += `${movie['title']}, ${movie['release_date']}n`;     });     await fs.writeFile('asyncAwaitMovies.csv', movieList); } 

También usaremos la palabra clave await cuando escribamos en un archivo con fs.writeFile().

Para completar esta función, debemos capturar los errores que nuestras promesas puedan producir. Lo haremos encapsulando nuestro código en un bloque try y catch:

asyncAwaitMovies.js

const axios = require('axios'); const fs = require('fs').promises;  async function saveMovies() {     try {         let response = await axios.get('https://ghibliapi.herokuapp.com/films');         let movieList = '';         response.data.forEach(movie => {             movieList += `${movie['title']}, ${movie['release_date']}n`;         });         await fs.writeFile('asyncAwaitMovies.csv', movieList);     } catch (error) {         console.error(`Could not save the Ghibli movies to a file: ${error}`);     } }  

Debido a que las promesas pueden fallar, encapsularemos nuestro código asíncrono con una cláusula try y catch. Con esto se capturarán todos los errores que se produzcan cuando las solicitudes HTTP o las operaciones de escritura en archivos fallen.

Por último, invocaremos nuestra función asíncrona saveMovies() para que se ejecute cuando iniciemos el programa con node.

asyncAwaitMovies.js

const axios = require('axios'); const fs = require('fs').promises;  async function saveMovies() {     try {         let response = await axios.get('https://ghibliapi.herokuapp.com/films');         let movieList = '';         response.data.forEach(movie => {             movieList += `${movie['title']}, ${movie['release_date']}n`;         });         await fs.writeFile('asyncAwaitMovies.csv', movieList);     } catch (error) {         console.error(`Could not save the Ghibli movies to a file: ${error}`);     } }  saveMovies(); 

A simple vista, parece un típico bloque de código síncrono de JavaScript. Tiene menos funciones que se pasan de un lado a otro, por lo que se ve más ordenado. Estos pequeños ajustes hacen que resulte más sencillo mantener el código asíncrono con async y await.

Pruebe esta iteración de nuestro programa ingresando lo siguiente en su terminal:

  • node asyncAwaitMovies.js

En su carpeta ghibliMovies, se creará un nuevo archivo asyncAwaitMovies.csv con el siguiente contenido:

asyncAwaitMovies.csv

Castle in the Sky, 1986 Grave of the Fireflies, 1988 My Neighbor Totoro, 1988 Kiki's Delivery Service, 1989 Only Yesterday, 1991 Porco Rosso, 1992 Pom Poko, 1994 Whisper of the Heart, 1995 Princess Mononoke, 1997 My Neighbors the Yamadas, 1999 Spirited Away, 2001 The Cat Returns, 2002 Howl's Moving Castle, 2004 Tales from Earthsea, 2006 Ponyo, 2008 Arrietty, 2010 From Up on Poppy Hill, 2011 The Wind Rises, 2013 The Tale of the Princess Kaguya, 2013 When Marnie Was There, 2014 

De esta manera, habrá utilizado las funciones de JavaScript async y await para administrar código asíncrono.

Conclusión

A través de este tutorial, aprendió sobre la forma en que JavaScript maneja las funciones de ejecución y administra las operaciones asíncronas con el bucle de eventos. Luego, escribió programas en los que se creó un archivo CSV después de realizar una solicitud HTTP para datos de películas utilizando varias técnicas de programación asíncrona. Primero, utilizó el enfoque obsoleto basado en la devolución de llamadaa. Luego utilizó promesas y, por último, async y await para hacer que la sintaxis de estas fuera más sucinta.

Al comprender el código asíncrono con Node.js, ahora puede desarrollar programas en los que se aprovechen los beneficios de la programación asíncrona, como los que dependen de las invocaciones de API. Consulte esta lista de API públicas. Para usarlas, tendrá que hacer solicitudes HTTP asíncronas como las que hicimos en este tutorial. Para un aprendizaje más profundo, intente crear una aplicación en la que se utilicen estas API a fin de poner en práctica las técnicas que aprendió aquí.