Índice
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
, catch
y finally
. Pero un año después, en ES7, el lenguaje agregó otro enfoque y dos nuevas palabras clave: async
y 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/catch
o async/await
qué) 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
then
and catch
y finally
son 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
, catch
y finally
, podríamos realizar una serie de acciones en función de si la Promesa se resuelve ( then
) o se rechaza ( catch
), mientras que finally
nos 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 then
métodos nos permite realizar operaciones sucesivas sobre una Promesa resuelta. Por ejemplo, un patrón típico para recuperar datos then
podrí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, async
y await
son palabras clave que hacen que el código de apariencia sincrónica sea asincrónico. Usamos async
al definir una función para indicar que devuelve una Promesa. Observe cómo la ubicación de la async
palabra 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 greeting
anterior, podríamos escribir:
async function doSomethingAsynchronous() { const value = await greeting;}
Luego podemos usar nuestra value
variable 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...finally
declaració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 async
funció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 await
si desea manejar el rechazo de la Promesa en un try...catch
bloque.
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, filterProlificAuthors
que 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 FETCH
arriba, 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 then
está anidado dentro del primero then
, mientras que el tercero then
es paralelo al segundo.
¿Nuestro código podría volverse un poco más legible si devolviéramos then
incluso código sincrónico? Podríamos dar filterProlificAuthors
su propio then
mé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 then
método cabe en una línea, pero no nos salva de múltiples niveles de anidamiento.
¿Qué pasa con el uso async
y 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/await
serán más evidentes a medida que cambien nuestros requisitos.
Parte 2
Introduzcamos un nuevo requisito. Esta vez, una vez que tengamos nuestra bios
matriz, 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 result
objeto al final.
Con then
, no es tan sencillo. En nuestra then
solución de la Parte 1, las variables books
y bios
nunca 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 books
a 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.all
para buscar los autores y los libros al mismo tiempo. Esto ayuda a limpiar then
un 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 then
solució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/await
sigue 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 then
puede 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 then
tenía diferentes compensaciones en cuanto a legibilidad. Por el contrario, async/await
se 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/await
nos proporciona una base fácil de entender para escribir una lógica más complicada, agregar muchos then
mé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/await
elige then/catch
.
(sh, ra, yk, il)Explora más en
- Herramientas
- Codificación
- javascript
Tal vez te puede interesar:
- ¿Deberían abrirse los enlaces en ventanas nuevas?
- 24 excelentes tutoriales de AJAX
- 70 técnicas nuevas y útiles de AJAX y JavaScript
- Más de 45 excelentes recursos y repositorios de fragmentos de código
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

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