Cómo escribir código asincrónico en Node.js

El autor seleccionó el Fondo de Internet Abierto/Libertad de Expresión para recibir una donación como parte del programa Escribir para Donaciones.

Introducción

Para muchos programas en JavaScript, el código se ejecuta tal como lo escribe el desarrollador, línea por línea. Esto se llama ejecución síncrona, porque las líneas se ejecutan una después de la otra, en el orden en que fueron escritas. Sin embargo, no todas las instrucciones que le das a la computadora necesitan ser atendidas de inmediato. Por ejemplo, si envías una solicitud de red, el proceso que ejecuta tu código tendrá que esperar a que los datos regresen antes de poder trabajar con ellos. En este caso, se perdería tiempo si no se ejecutara otro código mientras se espera a que se complete la solicitud de red. Para resolver este problema, los desarrolladores utilizan la programación asíncrona, en la que las líneas de código se ejecutan en un orden diferente al que fueron escritas. Con la programación asíncrona, podemos ejecutar otro código mientras esperamos que actividades largas, como las solicitudes de red, terminen.

El código JavaScript se ejecuta en un único hilo dentro de un proceso informático. Su código se procesa de forma síncrona en este hilo, con solo una instrucción ejecutada a la vez. Por lo tanto, si realizamos una tarea de larga duración en este hilo, todo el código restante se bloquea hasta que la tarea se complete. Al aprovechar las características de programación asincrónica de JavaScript, podemos desviar tareas de larga duración a un hilo en segundo plano para evitar este problema. Cuando la tarea se completa, el código que necesitamos para procesar los datos de la tarea se vuelve a colocar en el hilo principal único.

En este tutorial, aprenderás cómo JavaScript gestiona tareas asincrónicas con ayuda del Bucle de Eventos, que es una construcción de JavaScript que completa una nueva tarea mientras espera otra. Luego crearás un programa que utiliza programación asincrónica para solicitar una lista de películas de una API de Studio Ghibli y guardar los datos en un archivo CSV. El código asincrónico se escribirá de tres formas: mediante callbacks, promesas y con las palabras clave async/await.

Nota: Hasta la fecha de esta escritura, la programación asincrónica ya no se realiza solo con callbacks, pero aprender este método obsoleto puede proporcionar un gran contexto sobre por qué la comunidad de JavaScript ahora utiliza promesas. Las palabras clave async/await nos permiten utilizar promesas de una manera menos verbosa, y por lo tanto son la forma estándar de hacer programación asincrónica en JavaScript en el momento de escribir este artículo.

Requisitos previos

El bucle de eventos

Comencemos estudiando el funcionamiento interno de la ejecución de funciones en JavaScript. Comprender cómo se comporta esto te permitirá escribir código asíncrono de manera más deliberada y te ayudará a solucionar problemas en el código en el futuro.

A medida que el intérprete de JavaScript ejecuta el código, cada función que se llama se agrega al pila de llamadas de JavaScript. La pila de llamadas es una pila—una estructura de datos similar a una lista donde los elementos solo pueden agregarse en la parte superior y eliminarse de la parte superior. Las pilas siguen el principio de “Último en entrar, primero en salir” o LIFO. Si agregas dos elementos a la pila, el elemento más recientemente agregado es el primero en eliminarse.

Ilustremos con un ejemplo usando la pila de llamadas. Si JavaScript encuentra una función functionA() siendo llamada, se agrega a la pila de llamadas. Si esa función functionA() llama a otra función functionB(), entonces functionB() se agrega a la parte superior de la pila de llamadas. A medida que JavaScript completa la ejecución de una función, se elimina de la pila de llamadas. Por lo tanto, JavaScript ejecutará functionB() primero, lo eliminará de la pila cuando esté completo y luego terminará la ejecución de functionA() y lo eliminará de la pila de llamadas. Esta es la razón por la cual las funciones internas siempre se ejecutan antes que sus funciones externas.

Cuando JavaScript encuentra una operación asíncrona, como escribir en un archivo, la añade a una tabla en su memoria. Esta tabla almacena la operación, la condición para que se complete y la función que se llamará cuando esté completada. A medida que la operación se completa, JavaScript añade la función asociada a la cola de mensajes. Una cola es otra estructura de datos similar a una lista donde los elementos solo se pueden añadir en la parte inferior pero se eliminan desde la parte superior. En la cola de mensajes, si dos o más operaciones asíncronas están listas para que se ejecuten sus funciones, la operación asíncrona que se haya completado primero tendrá su función marcada para ser ejecutada primero.

Las funciones en la cola de mensajes están esperando ser añadidas a la pila de llamadas. El bucle de eventos es un proceso perpetuo que verifica si la pila de llamadas está vacía. Si lo está, entonces el primer elemento en la cola de mensajes se mueve a la pila de llamadas. JavaScript prioriza las funciones en la cola de mensajes sobre las llamadas de función que interpreta en el código. El efecto combinado de la pila de llamadas, la cola de mensajes y el bucle de eventos permite que el código JavaScript se procese mientras se gestionan actividades asíncronas.

Ahora que tienes una comprensión a alto nivel del bucle de eventos, sabes cómo se ejecutará el código asíncrono que escribas. Con este conocimiento, ahora puedes crear código asíncrono con tres enfoques diferentes: callbacks, promesas y async/await.

Programación Asíncrona con Callbacks

A callback function is one that is passed as an argument to another function, and then executed when the other function is finished. We use callbacks to ensure that code is executed only after an asynchronous operation is completed.

Durante mucho tiempo, los callbacks fueron el mecanismo más común para escribir código asíncrono, pero ahora en su mayoría han quedado obsoletos porque pueden hacer que el código sea confuso de leer. En este paso, escribirás un ejemplo de código asíncrono usando callbacks para que puedas usarlo como punto de referencia para ver la eficiencia aumentada de otras estrategias.

Hay muchas formas de usar funciones de callback en otra función. Generalmente, siguen esta estructura:

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

Aunque no es requerido sintácticamente por JavaScript o Node.js que la función de callback sea el último argumento de la función externa, es una práctica común que hace que los callbacks sean más fáciles de identificar. También es común para los desarrolladores de JavaScript usar una función anónima como callback. Las funciones anónimas son aquellas creadas sin un nombre. Por lo general, es mucho más legible cuando una función se define al final de la lista de argumentos.

Para demostrar los callbacks, crearemos un módulo Node.js que escriba una lista de películas de Studio Ghibli en un archivo. Primero, crea una carpeta que almacenará nuestro archivo JavaScript y su salida:

  1. mkdir ghibliMovies

Luego entra en esa carpeta:

  1. cd ghibliMovies

Comenzaremos haciendo una solicitud HTTP a la API de Studio Ghibli, la cual nuestra función de callback registrará los resultados. Para hacer esto, instalaremos una biblioteca que nos permita acceder a los datos de una respuesta HTTP en un callback.

En tu terminal, inicializa npm para que podamos tener una referencia para nuestros paquetes más tarde:Luego, instala la biblioteca request:

  1. npm init -y

Entonces, instala la biblioteca request:

  1. npm i request --save

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

  1. nano callbackMovies.js

En tu editor de texto, introduce el siguiente código. Comencemos 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, cargamos el módulo request que se instaló a través de npm. El módulo devuelve una función que puede realizar solicitudes HTTP; luego guardamos esa función en la constante request.

Luego hacemos la solicitud HTTP utilizando la función request(). Ahora imprimamos los datos de la solicitud HTTP en la consola agregando los 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 damos dos parámetros:

  • La URL de la página web a la que intentamos solicitar
  • A callback function that handles any errors or successful responses after the request is complete

Nuestra función de devolución de llamada tiene tres argumentos: error, response y body. Cuando la solicitud HTTP está completa, los argumentos se asignan automáticamente valores dependiendo del resultado. Si la solicitud no pudo enviarse, entonces error contendría un objeto, pero response y body serían null. Si hizo la solicitud con éxito, entonces la respuesta HTTP se almacena en response. Si nuestra respuesta HTTP devuelve datos (en este ejemplo obtenemos JSON) entonces los datos se establecen en body.

Nuestra función de devolución de llamada primero verifica si recibimos un error. Es una buena práctica verificar los errores en una devolución de llamada primero para que la ejecución de la devolución de llamada 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. Nuestro servidor puede que no siempre esté disponible, y las API pueden cambiar, lo que hace que las solicitudes que antes eran sensatas ahora sean incorrectas. Al verificar que el código de estado es 200, lo que significa que la solicitud fue “OK”, podemos tener la confianza de que nuestra respuesta es la esperada.

Finalmente, analizamos el cuerpo de la respuesta a un Array y recorremos cada película para registrar su nombre y año de lanzamiento.

Después de guardar y salir del archivo, ejecuta este script con:

  1. node callbackMovies.js

Obtendrás la siguiente salida:

Output
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

Hemos recibido con éxito una lista de películas de Studio Ghibli con el año en que fueron lanzadas. Ahora completemos este programa escribiendo la lista de películas que actualmente estamos registrando en un archivo.

Actualiza el archivo callbackMovies.js en tu editor de texto para incluir el siguiente código resaltado, que crea un archivo CSV con nuestros datos de 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, vemos que importamos el módulo fs. Este módulo es estándar en todas las instalaciones de Node.js, y contiene un método writeFile() que puede escribir de forma asíncrona en un archivo.

En lugar de registrar los datos en la consola, ahora los agregamos a una variable de cadena movieList. Luego usamos writeFile() para guardar el contenido de movieList en un nuevo archivo, callbackMovies.csv. Finalmente, proporcionamos una devolución de llamada a la función writeFile(), que tiene un argumento: error. Esto nos permite manejar casos en los que no podemos escribir en un archivo, por ejemplo, cuando el usuario en el que estamos ejecutando el proceso node no tiene esos permisos.

Guarda el archivo y ejecuta este programa Node.js una vez más con:

  1. node callbackMovies.js

En tu carpeta ghibliMovies, verás 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 tener en cuenta que escribimos en nuestro archivo CSV en la devolución de llamada de la solicitud HTTP. Una vez que el código está 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, haríamos otra función asíncrona que se llamaría en la devolución de llamada de writeFile(). Cuanto más código asíncrono tengamos, más funciones de devolución de llamada tendrán que estar anidadas.

Imaginemos que queremos ejecutar cinco operaciones asíncronas, cada una solo puede ejecutarse cuando otra esté completa. Si codificáramos esto, tendríamos algo como esto:

doSomething1(() => {
    doSomething2(() => {
        doSomething3(() => {
            doSomething4(() => {
                doSomething5(() => {
                    // acción final
                });
            });
        }); 
    });
});

Cuando los callbacks anidados tienen muchas líneas de código para ejecutar, se vuelven considerablemente más complejos e ilegibles. A medida que tu proyecto de JavaScript crece en tamaño y complejidad, este efecto se vuelve más pronunciado, hasta que eventualmente se vuelve inmanejable. Debido a esto, los desarrolladores ya no utilizan callbacks para manejar operaciones asíncronas. Para mejorar la sintaxis de nuestro código asíncrono, podemos usar promesas en su lugar.

Usando Promesas para una Programación Asíncrona Concisa

A promise is a JavaScript object that will return a value at some point in the future. Asynchronous functions can return promise objects instead of concrete values. If we get a value in the future, we say that the promise was fulfilled. If we get an error in the future, we say that the promise was rejected. Otherwise, the promise is still being worked on in a pending state.

Las promesas generalmente tienen la siguiente forma:

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

Como se muestra en esta plantilla, las promesas también utilizan 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 el método catch() para manejar cualquier error que surja mientras se está ejecutando la promesa.

Ahora, obtengamos experiencia de primera mano con promesas reescribiendo nuestro programa de Studio Ghibli para usar promesas en su lugar.

Axios es un cliente HTTP basado en promesas para JavaScript, así que vamos a instalarlo:

  1. npm i axios --save

Ahora, con tu editor de texto favorito, crea un nuevo archivo promiseMovies.js:

  1. nano promiseMovies.js

Nuestro programa hará una solicitud HTTP con axios y luego usará una versión especial basada en promesas de fs para guardar en un nuevo archivo CSV.

Escribe este código en promiseMovies.js para 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, almacenando la función devuelta en una constante llamada axios. Luego usamos el método axios.get() para enviar una solicitud HTTP a la API.

El método axios.get() devuelve una promesa. Vamos a encadenar 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']}`);
        });
    })

Vamos a analizar lo que está sucediendo. Después de hacer una solicitud GET HTTP con axios.get(), usamos la función then(), que solo se ejecuta cuando la promesa se cumple. En este caso, imprimimos las películas en la pantalla como lo hicimos en el ejemplo de callbacks.

Para mejorar este programa, agrega 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, importamos el módulo fs una vez más. Observa cómo después de la importación de fs tenemos .promises. Node.js incluye una versión basada en promesas de la biblioteca fs basada en callbacks, por lo que la compatibilidad con versiones anteriores no se rompe en proyectos heredados.

La primera función then() que procesa la solicitud HTTP ahora llama a fs.writeFile() en lugar de imprimir en la consola. Dado que importamos la versión basada en promesas de fs, nuestra función writeFile() devuelve otra promesa. Por lo tanto, agregamos otra función then() para cuando se cumpla la promesa de writeFile().

A promise can return a new promise, allowing us to execute promises one after the other. This paves the way for us to perform multiple asynchronous operations. This is called promise chaining, and it is analogous to nesting callbacks. The second then() is only called after we successfully write to the file.

Nota: En este ejemplo, no verificamos el código de estado HTTP como lo hicimos en el ejemplo de devolución de llamada. Por defecto, axios no cumple su promesa si recibe un código de estado que indica un error. Como tal, ya no necesitamos validarlo.

Para completar este programa, encadena la promesa con una función catch() como se destaca 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 va automáticamente a la función catch() si fue definida. Es por eso que solo tenemos una cláusula catch() incluso si tenemos dos operaciones asíncronas.

Confirmemos que nuestro programa produce la misma salida ejecutando:

  1. node promiseMovies.js

En tu carpeta ghibliMovies, verás el archivo promiseMovies.csv que contiene:

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

Con las promesas, podemos escribir un código mucho más conciso que utilizando solo devoluciones de llamada. La cadena de promesas de devoluciones de llamada es una opción más limpia que anidar devoluciones de llamada. Sin embargo, a medida que realizamos más llamadas asíncronas, nuestra cadena de promesas se vuelve más larga y difícil de mantener.

La verbosidad de las devoluciones de llamada y las promesas proviene de la necesidad de crear funciones cuando tenemos el resultado de una tarea asíncrona. Una mejor experiencia sería esperar un resultado asíncrono y ponerlo en una variable fuera de la función. De esa manera, podemos usar los resultados en las variables sin tener que hacer una función. Podemos lograr esto con las palabras clave async y await.

Escribir JavaScript con async/await

Las palabras clave async/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 como en cualquier otra función. Definimos una función con la palabra clave async para indicarle a JavaScript que es una función asíncrona que devuelve una promesa. Usamos la palabra clave await para indicarle a JavaScript que devuelva los resultados de la promesa en lugar de devolver la promesa en sí misma cuando se cumple.

En general, el uso de async/await se ve así:

async function() {
    await [Asynchronous Action]
}

Vamos a ver cómo el uso de async/await puede mejorar nuestro programa de Studio Ghibli. Usa tu editor de texto para crear y abrir un nuevo archivo asyncAwaitMovies.js:

  1. nano asyncAwaitMovies.js

En tu archivo JavaScript recién abierto, comencemos importando los mismos módulos que usamos en nuestro ejemplo de promesa:

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

Las importaciones son las mismas que en promiseMovies.js porque async/await utiliza promesas.

Ahora usamos 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() {}

Creamos una nueva función llamada saveMovies(), pero incluimos 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.

Usamos la palabra clave await para hacer una solicitud HTTP que 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(), hacemos una solicitud HTTP con axios.get() como antes. Esta vez, no lo encadenamos con una función then(). En cambio, agregamos await antes de que se llame. Cuando JavaScript ve await, solo ejecutará el código restante de la función después de que axios.get() termine la ejecución y establezca la variable response. El otro código guarda los datos de la película para que podamos escribir en un archivo.

Escribamos los datos de la película 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 usamos la palabra clave await cuando escribimos en el archivo con fs.writeFile().

Para completar esta función, necesitamos capturar los errores que pueden generar nuestras promesas. Hagámoslo encapsulando nuestro código en un bloque try/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}`);
    }
}

Dado que las promesas pueden fallar, encerramos nuestro código asíncrono con una cláusula try/catch. Esto capturará cualquier error que se lance cuando fallan las operaciones de solicitud HTTP o escritura de archivos.

Finalmente, llamemos a nuestra función asíncrona saveMovies() para que se ejecute cuando ejecutemos 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, esto parece un bloque de código JavaScript síncrono típico. Tiene menos funciones pasándose entre sí, lo cual luce un poco más ordenado. Estos pequeños ajustes hacen que el código asíncrono con async/await sea más fácil de mantener.

Prueba esta iteración de nuestro programa ingresando esto en tu terminal:

  1. node asyncAwaitMovies.js

En tu 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

Ahora has utilizado las características de JavaScript async/await para gestionar el código asíncrono.

Conclusión

En este tutorial, aprendiste cómo JavaScript maneja la ejecución de funciones y gestiona operaciones asíncronas con el ciclo de eventos. Luego escribiste programas que crearon un archivo CSV después de realizar una solicitud HTTP para datos de películas utilizando diversas técnicas de programación asíncrona. Primero, utilizaste el enfoque obsoleto basado en callbacks. Luego usaste promesas, y finalmente async/await para hacer que la sintaxis de promesas sea más sucinta.

Con tu comprensión del código asíncrono con Node.js, ahora puedes desarrollar programas que se beneficien de la programación asíncrona, como aquellos que dependen de llamadas a API. Echa un vistazo a esta lista de APIs públicas. Para usarlas, tendrás que hacer solicitudes HTTP asíncronas como lo hicimos en este tutorial. Para un estudio más profundo, intenta construir una aplicación que utilice estas APIs para practicar las técnicas que aprendiste aquí.

Source:
https://www.digitalocean.com/community/tutorials/how-to-write-asynchronous-code-in-node-js