Una comparación entre async/awaitversusthen/catch

 

 

 


Índice
  1. then, catchYfinally
  2. asyncYawait
  3. El problema
  4. La solución
    1. Parte 1
    2. Parte 2
    3. parte 3
  5. Conclusión

En JavaScript, hay dos formas principales de manejar código asincrónico: then/catch(ES6) y async/await(ES7). Estas sintaxis nos brindan la misma funcionalidad subyacente, pero afectan la legibilidad y el alcance de diferentes maneras. En este artículo, veremos cómo una sintaxis se presta para un código mantenible, mientras que la otra nos lleva al infierno de las devoluciones de llamadas.

 

JavaScript ejecuta el código línea por línea, pasando a la siguiente línea de código solo después de que se haya ejecutado la anterior. Pero ejecutar un código como este sólo puede llevarnos hasta cierto punto. A veces, necesitamos realizar tareas que tardan mucho tiempo o son impredecibles en completarse: recuperar datos o desencadenar efectos secundarios a través de una API, por ejemplo.

En lugar de permitir que estas tareas bloqueen el hilo principal de JavaScript, el lenguaje nos permite ejecutar ciertas tareas en paralelo. ES6 vio la introducción del objeto Promise, así como nuevos métodos para manejar la ejecución de estas Promesas: then, catchy finally. Pero un año después, en ES7, el lenguaje agregó otro enfoque y dos nuevas palabras clave: asyncy await.

Este artículo no explica el JavaScript asincrónico; Hay muchos buenos recursos disponibles para eso. En cambio, aborda un tema menos cubierto: ¿qué sintaxis ( then/catcho async/awaitqué) es mejor? En mi opinión, a menos que una biblioteca o un código base heredado le obligue a utilizar then/catch, la mejor opción en términos de legibilidad y mantenimiento es async/await. Para demostrarlo, usaremos ambas sintaxis para resolver el mismo problema. Al cambiar ligeramente los requisitos, debería quedar claro qué enfoque es más fácil de modificar y mantener.

Comenzaremos recapitulando las características principales de cada sintaxis, antes de pasar a nuestro escenario de ejemplo.

 

then, catchYfinally

thenand catchy finallyson métodos del objeto Promise y están encadenados uno tras otro. Cada uno toma una función de devolución de llamada como argumento y devuelve una Promesa.

Por ejemplo, creemos una instancia de una Promesa simple:

const greeting = new Promise((resolve, reject) = { resolve("Hello!");});

Usando then, catchy finally, podríamos realizar una serie de acciones en función de si la Promesa se resuelve ( then) o se rechaza ( catch), mientras que finallynos permite ejecutar código una vez que se resuelve la Promesa, independientemente de si se resolvió o se rechazó:

greeting .then((value) = { console.log("The Promise is resolved!", value); }) .catch((error) = { console.error("The Promise is rejected!", error); }) .finally(() = { console.log( "The Promise is settled, meaning it has been resolved or rejected." ); });

Para los propósitos de este artículo, solo necesitamos usar then. Encadenar múltiples thenmétodos nos permite realizar operaciones sucesivas sobre una Promesa resuelta. Por ejemplo, un patrón típico para recuperar datos thenpodría verse así:

fetch(url) .then((response) = response.json()) .then((data) = { return { data: data, status: response.status, }; }) .then((res) = { console.log(res.data, res.status); });

asyncYawait

Por el contrario, asyncy awaitson palabras clave que hacen que el código de apariencia sincrónica sea asincrónico. Usamos asyncal definir una función para indicar que devuelve una Promesa. Observe cómo la ubicación de la asyncpalabra clave depende de si usamos funciones regulares o funciones de flecha:

async function doSomethingAsynchronous() { // logic}const doSomethingAsynchronous = async () = { // logic};

await, mientras tanto, se usa antes de una Promesa. Pausa la ejecución de una función asincrónica hasta que se resuelva la Promesa. Por ejemplo, para esperar lo greetinganterior, podríamos escribir:

async function doSomethingAsynchronous() { const value = await greeting;}

Luego podemos usar nuestra valuevariable como si fuera parte de un código síncrono normal.

En cuanto al manejo de errores, podemos incluir cualquier código asincrónico dentro de una try...catch...finallydeclaración, así:

async function doSomethingAsynchronous() { try { const value = await greeting; console.log("The Promise is resolved!", value); } catch((error) { console.error("The Promise is rejected!", error); } finally { console.log( "The Promise is settled, meaning it has been resolved or rejected." ); }}

Finalmente, al devolver una Promesa dentro de una asyncfunción, no es necesario usar await. Entonces la siguiente es una sintaxis aceptable.

async function getGreeting() { return greeting;}

Sin embargo, hay una excepción a esta regla: es necesario escribir return awaitsi desea manejar el rechazo de la Promesa en un try...catchbloque.

 

async function getGreeting() { try { return await greeting; } catch (e) { console.error(e); }}

Usar ejemplos abstractos puede ayudarnos a comprender cada sintaxis, pero es difícil ver por qué una podría ser preferible a la otra hasta que veamos un ejemplo.

El problema

Imaginemos que necesitamos realizar una operación en un gran conjunto de datos para una librería. Nuestra tarea es encontrar todos los autores que hayan escrito más de 10 libros en nuestro conjunto de datos y devolver su biografía. Tenemos acceso a una biblioteca con tres métodos asincrónicos:

// getAuthors - returns all the authors in the database// getBooks - returns all the books in the database// getBio - returns the bio of a specific author

Nuestros objetos se ven así:

// Author: { id: "3b4ab205", name: "Frank Herbert Jr.", bioId: "1138089a" }// Book: { id: "e31f7b5e", title: "Dune", authorId: "3b4ab205" }// Bio: { id: "1138089a", description: "Franklin Herbert Jr. was an American science-fiction author..." }

Por último, necesitaremos una función auxiliar, filterProlificAuthorsque toma todas las publicaciones y todos los libros como argumentos y devuelve los ID de aquellos autores con más de 10 libros: Actualidad y noticias

function filterProlificAuthors() { return authors.filter( ({ id }) = books.filter(({ authorId }) = authorId === id).length 10 );}

La solución

Parte 1

Para resolver este problema, necesitamos buscar a todos los autores y todos los libros, filtrar nuestros resultados según los criterios dados y luego obtener la biografía de los autores que cumplan con esos criterios. En pseudocódigo, nuestra solución podría verse así:

FETCH all authorsFETCH all booksFILTER authors with more than 10 booksFOR each filtered author FETCH the author’s bio

Cada vez que vemos FETCHarriba, necesitamos realizar una tarea asincrónica. Entonces, ¿cómo podríamos convertir esto en JavaScript? Primero, veamos cómo podríamos codificar estos pasos usando then:

getAuthors().then((authors) = getBooks() .then((books) = { const prolificAuthorIds = filterProlificAuthors(authors, books); return Promise.all(prolificAuthorIds.map((id) = getBio(id))); }) .then((bios) = { // Do something with the bios }));

Este código hace el trabajo, pero hay algunos anidamientos que pueden dificultar su comprensión de un vistazo. El segundo thenestá anidado dentro del primero then, mientras que el tercero thenes paralelo al segundo.

 

¿Nuestro código podría volverse un poco más legible si devolviéramos thenincluso código sincrónico? Podríamos dar filterProlificAuthorssu propio thenmétodo, como a continuación:

getAuthors().then((authors) = getBooks() .then((books) = filterProlificAuthors(authors, books)) .then((ids) = Promise.all(ids.map((id) = getBio(id)))) .then((bios) = { // Do something with the bios }));

Esta versión tiene la ventaja de que cada thenmétodo cabe en una línea, pero no nos salva de múltiples niveles de anidamiento.

¿Qué pasa con el uso asyncy await? Nuestro primer paso hacia una solución podría verse así:

async function getBios() { const authors = await getAuthors(); const books = await getBooks(); const prolificAuthorIds = filterProlificAuthors(authors, books); const bios = await Promise.all(prolificAuthorIds.map((id) = getBio(id))); // Do something with the bios}

A mí esta solución ya me parece más sencilla. No implica anidamiento y se puede expresar fácilmente en solo cuatro líneas, todas con el mismo nivel de sangría. Sin embargo, los beneficios async/awaitserán más evidentes a medida que cambien nuestros requisitos.

Parte 2

Introduzcamos un nuevo requisito. Esta vez, una vez que tengamos nuestra biosmatriz, queremos crear un objeto que contenga bios, el número total de autores y el número total de libros.

Esta vez comenzaremos con async/await:

async function getBios() { const authors = await getAuthors(); const books = await getBooks(); const prolificAuthorIds = filterProlificAuthors(authors, books); const bios = await Promise.all(prolificAuthorIds.map((id) = getBio(id))); const result = { bios, totalAuthors: authors.length, totalBooks: books.length, };}

¡Fácil! No tenemos que hacer nada con nuestro código existente, ya que todas las variables que necesitamos ya están dentro del alcance. Podemos simplemente definir nuestro resultobjeto al final.

Con then, no es tan sencillo. En nuestra thensolución de la Parte 1, las variables booksy biosnunca están en el mismo alcance. Si bien podríamos introducir una variable global books, eso contaminaría el espacio de nombres global con algo que solo necesitamos en nuestro código asincrónico. Sería mejor reformatear nuestro código. Entonces, ¿cómo podríamos hacerlo?

Una opción sería introducir un tercer nivel de anidamiento:

getAuthors().then((authors) = getBooks().then((books) = { const prolificAuthorIds = filterProlificAuthors(authors, books); return Promise.all(prolificAuthorIds.map((id) = getBio(id))).then( (bios) = { const result = { bios, totalAuthors: authors.length, totalBooks: books.length, }; } ); }));

Alternativamente, podríamos usar la sintaxis de desestructuración de matrices para ayudar a pasar booksa través de la cadena en cada paso:

getAuthors().then((authors) = getBooks() .then((books) = [books, filterProlificAuthors(authors, books)]) .then(([books, ids]) = Promise.all([books, ...ids.map((id) = getBio(id))]) ) .then(([books, bios]) = { const result = { bios, totalAuthors: authors.length, totalBooks: books.length, }; }));

Para mí, ninguna de estas soluciones es particularmente legible. Es difícil determinar, de un vistazo, qué variables son accesibles y dónde.

 

parte 3

Como optimización final, podemos mejorar el rendimiento de nuestra solución y limpiarla un poco usando Promise.allpara buscar los autores y los libros al mismo tiempo. Esto ayuda a limpiar thenun poco nuestra solución:

Promise.all([getAuthors(), getBooks()]).then(([authors, books]) = { const prolificAuthorIds = filterProlificAuthors(authors, books); return Promise.all(prolificAuthorIds.map((id) = getBio(id))).then((bios) = { const result = { bios, totalAuthors: authors.length, totalBooks: books.length, }; });});

Esta puede ser la mejor thensolución del grupo. Elimina la necesidad de múltiples niveles de anidamiento y el código se ejecuta más rápido.

Sin embargo, async/awaitsigue siendo más sencillo:

async function getBios() { const [authors, books] = await Promise.all([getAuthors(), getBooks()]); const prolificAuthorIds = filterProlificAuthors(authors, books); const bios = await Promise.all(prolificAuthorIds.map((id) = getBio(id))); const result = { bios, totalAuthors: authors.length, totalBooks: books.length, };}

¡No hay anidamiento, solo un nivel de sangría y muchas menos posibilidades de confusión basada en corchetes!

Conclusión

A menudo, el uso de métodos encadenados thenpuede requerir modificaciones complicadas, especialmente cuando queremos asegurarnos de que ciertas variables estén dentro del alcance. Incluso para un escenario simple como el que discutimos, no había una mejor solución obvia: cada una de las cinco soluciones utilizadas thentenía diferentes compensaciones en cuanto a legibilidad. Por el contrario, async/awaitse prestó a una solución más legible que necesitaba cambiar muy poco cuando se modificaban los requisitos de nuestro problema.

En aplicaciones reales, los requisitos de nuestro código asincrónico a menudo serán más complejos que el escenario presentado aquí. Si bien async/awaitnos proporciona una base fácil de entender para escribir una lógica más complicada, agregar muchos thenmétodos puede fácilmente forzarnos a seguir el camino hacia el infierno de las devoluciones de llamadas, con muchos corchetes y niveles de sangría que hacen que no quede claro dónde termina un bloque y comienza el siguiente.

Por esa razón, si tienes la opción, async/awaitelige then/catch.

(sh, ra, yk, il)Explora más en

  • Herramientas
  • Codificación
  • javascript





Tal vez te puede interesar:

  1. ¿Deberían abrirse los enlaces en ventanas nuevas?
  2. 24 excelentes tutoriales de AJAX
  3. 70 técnicas nuevas y útiles de AJAX y JavaScript
  4. Más de 45 excelentes recursos y repositorios de fragmentos de código

Una comparación entre async/awaitversusthen/catch

Una comparación entre async/awaitversusthen/catch

then, catchYfinallyasyncYawaitEl problemaLa soluciónConclusiónÍndice then, catchYfinally

programar

es

https://aprendeprogramando.es/static/images/programar-una-comparacion-entre-asyncawaitversusthencatch-1074-0.jpg

2025-01-15

 

Una comparación entre async/awaitversusthen/catch
Una comparación entre async/awaitversusthen/catch

Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

 

 

Update cookies preferences