One place for hosting & domains

      código

      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

      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:

      Luego acceda a esa carpeta:

      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:

      A continuación, instale la biblioteca request:

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

      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:

      Verá el siguiente resultado:

      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

      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:

      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:

      Ahora, con su editor de texto preferido, cree un archivo nuevo 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:

      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:

      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:

      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í.



      Source link

      Como escrever um código assíncrono em Node.js


      O autor selecionou a Open Internet/Free Speech Fund para receber uma doação como parte do programa Write for DOnations.

      Introdução

      Com muitos programas em JavaScript, o código é executado da maneira como o programador o escreveu — linha por linha. A isto chamamos de execução síncrona, pois as linhas são executadas uma após a outra, na ordem em que foram escritas. No entanto, nem toda instrução que você dá ao computador precisa ser atendida imediatamente. Por exemplo, caso envie uma solicitação de rede, o processo que executa seu código terá que esperar pelo retorno dos dados, antes de poder trabalhar neles. Nesse caso, seria um desperdício de tempo se o computador não executasse outro código enquanto aguarda a solicitação da rede ser concluída. Para resolver esse problema, os desenvolvedores usam a programação assíncrona, na qual as linhas do código são executadas em uma ordem diferente daquela em que foram escritas. Com a programação assíncrona, podemos executar outros códigos enquanto esperamos por atividades longas terminarem, como os pedidos de rede.

      O código JavaScript é executado em um único thread dentro de um processo de computador. Seu código é processado de maneira sincronizada nesse thread, com apenas uma instrução sendo executada por vez. Portanto, caso fôssemos realizar uma tarefa de execução demorada nesse thread, todo o código restante ficaria bloqueado até que a tarefa estivesse concluída. Para evitar esse problema, podemos aproveitar-nos dos recursos de programação assíncrona do JavaScript, descarregando tarefas de execução demorada em um thread em segundo plano. Quando a tarefa é concluída, o código que precisamos para processar os dados da tarefa é colocado de volta no thread único principal.

      Neste tutorial, você aprenderá como o JavaScript gerencia tarefas assíncronas com a ajuda do Loop de eventos, que consiste em um constructo do JavaScript que conclui uma nova tarefa enquanto aguarda por outra. Depois, criará um programa que utilize a programação assíncrona para solicitar uma lista de filmes de uma API do Studio Ghibli e salvará os dados em um arquivo CSV. O código assíncrono será escrito de três maneiras: callbacks (retornos de chamadas), promises (promessas) e com as palavras-chave async/await.

      Nota: no momento desta publicação, a programação assíncrona não é mais feita usando apenas com os callbacks. Entretanto, aprender esse método obsoleto pode proporcionar um ótimo contexto quanto ao motivo pelo qual a comunidade JavaScript agora usa as promises. As palavras-chave async/await nos permitem usar as promises de uma maneira menos detalhada. Assim, no momento em que este artigo foi escrito, essa era a maneira padrão de se fazer a programação assíncrona em JavaScript.

      Pré-requisitos

      O loop de eventos

      Vamos começar estudando o funcionamento interno da execução de funções em JavaScript. Entender como as funções se comportam permitirá que você escreva um código assíncrono de maneira mais consciente, bem como ajudará você a solucionar problemas do código no futuro.

      À medida que o programa interpretador de JavaScript executa o código, cada função que é chamada é adicionada à pilha de chamadas do JavaScript. A pilha de chamadas é uma* pilha* — uma estrutura de dados do tipo lista, na qual os itens apenas podem ser adicionados ao topo e removidos do topo. As pilhas seguem o princípio “Último a entrar, primeiro a sair”, ou princípio LIFO (do inglês “Last in, first out”). Se adicionar dois itens na pilha, o item adicionado por último será removido primeiro.

      Vamos ilustrar com um exemplo usando a pilha de chamadas. Se o JavaScript encontra uma função functionA() sendo chamada, ela é adicionada à pilha de chamadas. Se essa função, functionA() chama outra função, functionB(), então a functionB() é adicionada ao topo da pilha de chamadas. À medida que o JavaScript completa a execução de uma função, ela é removida da pilha de chamadas. Portanto, o JavaScript executará a functionB() primeiro, irá removê-la da pilha quando terminar para, então, terminar a execução da functionA() e remover esta da pilha de chamadas. É por isso que as funções internas são sempre executadas antes de suas funções externas.

      Quando o JavaScript encontra uma operação assíncrona, como gravar algo em um arquivo, ele adiciona essa operação a uma tabela de sua memória. Essa tabela armazena a operação, a condição para que ela seja completada e a função a ser chamada quando ela for concluída. Quando a operação é concluída, o JavaScript adiciona a função associada à fila de mensagens. Uma fila é outra estrutura de dados do tipo lista. Nela, os itens apenas podem ser adicionados ao final, mas removidos do topo. Na fila de mensagens, se duas ou mais operações assíncronas estiverem prontas para que suas funções sejam executadas, a operação assíncrona que foi concluída primeiro terá sua função marcada para executar primeiro.

      As funções na fila de mensagens estão esperando para serem adicionadas à pilha de chamadas. O loop de eventos é um processo contínuo que verifica se a pilha de chamadas está vazia. Se estiver, então o primeiro item na fila de mensagens é movido para a pilha de chamadas. O JavaScript prioriza as funções na fila de mensagens, em detrimento das chamadas de função que ele interpreta no código. O efeito combinado da pilha de chamadas, da fila de mensagens e do loop de eventos permite que código em JavaScript seja processado enquanto gerencia as atividades assíncronas.

      Agora que possui uma comprensão abrangente sobre o loop de eventos, você sabe como o código assíncrono que você escrever será executado. Com esse conhecimento, agora você pode criar um código assíncrono com três abordagens diferentes: callbacks, promises e async/await.

      Uma função callback é aquela que é enviada para outra função como um argumento e, em seguida, é executada – quando a outra função tiver sido finalizada. Usamos as callbacks para garantir que o código somente será executado depois da conclusão de uma operação assíncrona.

      Por muito tempo, os callbacks foram o mecanismo mais comum para se escrever códigos assíncronos. Agora, porém, eles se tornaram, em grande parte, obsoletos, uma vez que podem tornar um código confuso para ler. Neste passo, você escreverá um exemplo de código assíncrono usando callbacks, de modo que você possa usá-lo como um parâmetro de comparação para perceber a eficiência das demais estratégias.

      Existem muitas maneiras de se usar as funções de callback em outra função. Geralmente, elas adotam esta estrutura:

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

      Embora nem o JavaScript nem o Node.js tenham a exigência sintática de trazer a função de callback como o último argumento da função externa, trata-se de uma prática comum para facilitar a identificação dos callbacks. Também é comum para os desenvolvedores em JavaScript usar uma função anônima como um callback. As funções anônimas são aquelas criadas sem um nome. Normalmente, uma função fica muito mais legível quando definida no final da lista de argumentos.

      Para demonstrar os callbacks, vamos criar um módulo do Node.js que grava uma lista dos filmes do Studio Ghibli em um arquivo. Primeiro, crie uma pasta que armazenará nosso arquivo JavaScript e seu resultado:

      Então, acesse aquela pasta:

      Começaremos fazendo uma solicitação HTTP à API do Studio Ghibli, da qual nossa função de callback registrará os resultados. Para tanto, instalaremos uma biblioteca que nos permitirá acessar os dados de uma resposta do HTTP em um callback.

      No seu terminal, inicialize o npm para termos uma referência para nossos pacotes mais tarde:

      Em seguida, instale a biblioteca request (solicitação):

      Agora, abra um novo arquivo chamado callbackMovies.js em um editor de texto como o nano:

      No seu editor de texto, digite o código a seguir. Vamos começar enviando uma solicitação HTTP com o módulo request:

      callbackMovies.js

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

      Na primeira linha, carregaremos o módulo request – o qual foi instalado via npm [NT: gerenciador de pacotes para a linguagem de programação JavaScript]. O módulo retorna uma função que pode fazer pedidos via HTTP; então, salvamos essa função na constante request.

      Depois, realizamos a solicitação HTTP usando a função request(). Agora, vamos imprimir os dados da solicitação HTTP no console, adicionando as alterações destacadas:

      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']}`);
          });
      });
      

      Quando usamos a função request(), damos a ela dois parâmetros:

      • O URL do site que estamos tentando solicitar
      • Uma função de callback que lide com erros ou respostas bem-sucedidas depois que a solicitação for concluída

      Nossa função de callback tem três argumentos: error (erro), response (reposta) e body (corpo). Quando a solicitação HTTP é concluída, os argumentos recebem valores automaticamente, dependendo do resultado. Caso houvesse falha no envio da solicitação, então o error conteria um objeto, mas o argumento response e body seriam null (nulo). Se o pedido fosse bem-sucedido, então a resposta HTTP seria armazenada em response. Se nossa resposta HTTP retornasse dados (neste exemplo, obtivemos JSON), então os dados seriam definidos em body.

      Primeiro, nossa função de callback verifica se recebemos um erro. Verificar primeiro se há erros em um callback é uma boa prática. Dessa maneira, a execução do callback não continuará com dados faltando. Neste caso, registramos o erro e a execução da função. Em seguida, verificamos o código do status da resposta. Nem sempre nosso servidor pode estar disponível e as APIs podem sofrer alterações, fazendo com que solicitações, antes adequadas, tornem-se incorretas. Ao verificar se o código de status é 200 – que significa que o pedido estava “OK”, podemos confiar que nossa resposta será a que esperamos que seja.

      Por fim, analisamos o corpo da resposta em uma Array (matriz) e executamos um loop em cada filme para registrar seu nome e ano de lançamento.

      Após salvar e sair do arquivo, execute este script com:

      Você receberá o seguinte resultado:

      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

      Felizmente, recebemos uma lista dos filmes do Studio Ghibli, contendo o ano em que foram lançados. Agora, vamos completar esse programa, escrevendo a lista de filmes que estamos registrando em um arquivo no momento.

      Atualize o arquivo callbackMovies.js em seu editor de texto, de modo a incluir o seguinte código destacado, o qual cria um arquivo CSV com os nossos dados dos filmes:

      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');;
          });
      });
      

      Ao notar as alterações destacadas, vemos que importamos o módulo fs. Esse módulo é padrão em todas as instalações do Node.js e contém um método writeFile() que pode escrever em um arquivo de maneira assíncrona.

      Em vez de registrar os dados no console, agora nós vamos adicioná-los a uma variável de string – movieList. Então, usamos o writeFile() para salvar o conteúdo da movieList em um novo arquivo — callbackMovies.csv. Por fim, fornecemos um callback à função writeFile(), que tem um argumento: error. Isso nos permite lidar com casos em que não conseguimos escrever em um arquivo, como, por exemplo, quando o usuário em que estamos executando o processo node não tem essas permissões.

      Salve o arquivo e execute esse programa Node.js novamente com:

      Em sua pasta ghibliMovies, você verá o callbackMovies.csv, que tem o seguinte conteúdo:

      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
      

      É importante notar que estamos escrevendo em nosso arquivo CSV no callback da solicitação HTTP. Assim que o código estiver na função de callback, ele apenas gravará no arquivo após a solicitação HTTP ser finalizada. Se quiséssemos nos comunicar com um banco de dados – depois de termos escrito nosso arquivo CSV, faríamos outra função assíncrona, a qual seria chamada no callback da função writeFile(). Quanto mais código assíncrono tivermos, mais funções de callback precisam ser aninhadas.

      Imaginemos um cenário em que queiramos executar cinco operações assíncronas, cada uma podendo ser executada apenas quando outra for concluída. Se tivéssemos que codificar isso, teríamos algo parecido com o seguinte:

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

      Quando os callbacks aninhados têm muitas linhas de código a serem executadas, eles se tornam consideravelmente mais complexos e ilegíveis. À medida que seu projeto JavaScript cresce em tamanho e complexidade, esse efeito se tornará mais visível, até que, no final das contas, ele se torna não gerenciável. Por isso, os desenvolvedores não usam mais callbacks para lidar com operações assíncronas. Para melhorar a sintaxe do nosso código assíncrono, podemos usar promises como alternativa.

      Usando promises para obter uma programação assíncrona concisa

      Uma promise é um objeto JavaScript que retornará um valor em algum momento no futuro. As funções assíncronas podem retornar objetos promise, em vez de valores concretos. Se recebermos um valor no futuro, dizemos que a promessa (promise) foi cumprida. Se recebermos um erro no futuro, dizemos que a promessa foi rejeitada. Caso contrário, a promessa ainda estará sendo trabalhada em um estado pendente.

      As promises geralmente adotam a seguinte forma:

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

      Como mostrado nesse modelo, as promises também usam funções de callback. Temos uma função de callback para o método then(), que é executado quando uma promise é realizada. Também temos uma função de callback para o método catch() para lidar com todo erro que aparecer enquanto a promessa é executada.

      Vamos ter uma experiência direta com as promises, reescrevendo nosso programa do Studio Ghibli para que use promises como alternativa.

      O Axios é um cliente HTTP baseado em promises para JavaScript. Assim, vamos prosseguir e instalá-lo:

      Agora, com o editor de sua escolha, crie um novo arquivo promiseMovies.js:

      Nosso programa fará uma solicitação HTTP com o axios e depois usará uma versão especial do fs – baseada em promise – para salvar em um novo arquivo CSV.

      Digite este código em promiseMovies.js para que possamos carregar o Axios e enviar uma solicitação HTTP para a API de filmes:

      promiseMovies.js

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

      Na primeira linha, carregamos o módulo axios, armazenando a função retornada em uma constante chamada axios. Em seguida, usamos o método axios.get() para enviar uma solicitação HTTP para a API.

      O método axios.get() retorna uma promise. Vamos encadear aquela promise para que possamos imprimir a lista dos filmes Ghibli no console:

      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 detalhar o que está acontecendo. Após realizar uma solicitação HTTP GET com o axios.get(), usamos a função then(), que é executada apenas quando a promise é realizada. Neste caso, imprimimos os filmes na tela como fizemos no exemplo dos callbacks.

      Para melhorar esse programa, adicione o código destacado para escrever os dados HTTP em um arquivo:

      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');
          })
      

      Além disso, importamos o módulo fs novamente. Note que, após a importação do fs, agora temos a extensão .promises. O Node.js inclui uma versão baseada em promises da biblioteca fs baseada em callbacks, de modo que a retrocompatibilidade não é quebrada em projetos herdados.

      A primeira função then() que processa a solicitação HTTP agora chama o fs.writeFile(), em vez de imprimir no console. Como importamos a versão baseada em promises do fs, nossa função writeFile() retorna outra promise. Desta forma, anexamos outra função then() para quando a promise da writeFile() for realizada.

      Uma promise pode retornar uma nova promise, permitindo que executemos promises uma após a outra. Isso nos possibilita realizar múltiplas operações assíncronas. Isso se chama encadeamento de promises, sendo comparáveis às callbacks aninhadas. O segundo then() é chamado apenas após gravarmos com sucesso no arquivo.

      Nota: neste exemplo, não verificamos o código de status do HTTP como fizemos no exemplo das callbacks. Por padrão, o axios não realiza duas promises caso receber um código de status indicando um erro. Desta forma, não precisamos mais validá-lo.

      Para completar esse programa, encadeie a promise com uma função catch(), como destacado a seguir:

      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}`);
          });
      

      Se alguma promise deixar de ser realizada na cadeia de promises, o JavaScript automaticamente vai para a função catch() – caso ela tenha sido definida. É por isso que temos apenas uma cláusula catch(), apesar de termos duas operações assíncronas.

      Vamos confirmar se nosso programa produz o mesmo resultado, executando:

      Na sua pasta ghibliMovies, você verá o arquivo promiseMovies.csv, que contém:

      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
      

      Com as promises, podemos escrever um código muito mais conciso do que usando apenas callbacks. A cadeia de promises das callbacks é uma opção mais organizada do que o aninhamento de callbacks. No entanto, à medida que fazemos mais chamadas assíncronas, nossa cadeia de promises se torna mais longa e difícil de manter.

      O detalhamento das funções callbacks e promises vem da necessidade de se criar funções quando temos o resultado de uma tarefa assíncrona. Uma prática melhor seria aguardar por um resultado assíncrono e colocá-lo em uma variável fora da função. Dessa maneira, podemos usar os resultados nas variáveis sem ter que criar uma função. Podemos conseguir isso com as palavras-chave async e await.

      As palavras-chave async/await fornecem uma sintaxe alternativa ao trabalhar com promises. Em vez de ter o resultado de uma promise disponível no método then(), o resultado é retornado como um valor, assim como em qualquer outra função. Definimos uma função com a palavra-chave async para dizer ao JavaScript que é uma função assíncrona que retorna uma promise. Usamos a palavra-chave await para dizer ao JavaScript para retornar os resultados da promise, em vez de retornar a promise, propriamente dita, quanto ela for realizada.

      De um modo geral, o uso de async/await fica com a seguinte aparência:

      async function() {
          await [Asynchronous Action]
      }
      

      Vejamos de que maneira o uso de async/await pode melhorar nosso programa do Studio Ghibli. Use seu editor de texto para criar e abrir um novo arquivo asyncAwaitMovies.js:

      No seu arquivo em JavaScript recém-criado, vamos começar importando os mesmos módulos que usamos em nosso exemplo de promise:

      asyncAwaitMovies.js

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

      As importações são as mesmas do promiseMovies.js, pois async/await usa promises.

      Agora, usamos a palavra-chave async para criar uma função com nosso código assíncrono:

      asyncAwaitMovies.js

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

      Criamos uma nova função chamada saveMovies(), mas incluímos async no início de sua definição. Isso é importante, pois somente podemos usar a palavra-chave await em uma função assíncrona.

      Utilize a palavra-chave await para fazer uma solicitação HTTP que recebe a lista de filmes da API do 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`;
          });
      }
      

      Em nossa função saveMovies(), fazemos uma solicitação HTTP com o axios.get(), como antes. Desta vez, não a encadearemos com uma função then(). Em vez disso, adicionamos await antes que ela seja chamada. Quando o JavaScript vê await, ele somente executará o código restante da função depois que a função axios.get() terminar a execução e definir a variável response. O outro código salva os dados dos filmes para que possamos gravar em um arquivo.

      Vamos gravar os dados dos filmes em um arquivo:

      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);
      }
      

      Também usamos a palavra-chave await quando escrevemos no arquivo com fs.writeFile().

      Para completar essa função, precisamos capturar os erros que nossas promises podem gerar. Vamos fazer isso, encapsulando nosso código em um bloco try/catch (tentar/caputar):

      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}`);
          }
      }
      
      

      Como as promises podem falhar, envolvemos nosso código assíncrono com uma cláusula try/catch. Isso captará quaisquer erros que forem gerados quando as operações de solicitação HTTP ou as operações de gravação de arquivos falharem.

      Por fim, vamos chamar nossa função assíncrona saveMovies() para que ela seja executada quando executarmos o programa com o 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();
      

      De imediato, o programa fica parecido com um bloco de código JavaScript síncrono típico. Ele tem menos funções sendo passadas, fazendo-o parecer mais organizado. Esses pequenos ajustes tornam o código assíncrono com async/await mais fáceis de manter.

      Teste essa iteração do nosso programa, digitando isto em seu terminal:

      Em sua pasta ghibliMovies, um novo arquivo asyncAwaitMovies.csv será criado com o seguinte conteúdo:

      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
      

      Agora, você usou os recursos async/await do JavaScript para gerenciar um código assíncrono.

      Conclusão

      Neste tutorial, você aprendeu como o JavaScript lida com a execução de funções e o gerenciamento de operações assíncronas com o loop de eventos. Na sequência, você escreveu programas que criaram um arquivo CSV depois de fazer uma solicitação HTTP por dados de filmes, usando várias técnicas de programação assíncrona. Primeiro, você usou a abordagem obsoleta baseada em callbacks. Então, usou promises e, por fim, async/await para tornar a sintaxe da promise mais sucinta.

      Com o conhecimento sobre códigos assíncronos com o Node.js, agora você pode desenvolver programas que se beneficiem da programação assíncrona, tais como aqueles que dependem de chamadas da API. Veja esta lista de APIs públicas. Para usá-las, você terá que fazer solicitações HTTP assíncronas, como fizemos neste tutorial. Para avançar nesse estudo, tente compilar um app que utilize essas APIs para praticar as técnicas que aprendeu aqui.



      Source link