DanLevy.net

Deja de intentar forzar async/await

Las promesas están muy de moda ahora.

Hero image for Deja de intentar forzar async/await

Desde el principio de lostiempos, los desarrolladores han peleado muchas batallas sin sentido. Desde el clásico “Tabs vs. Spaces” hasta el eterno debate “Mac vs. PC”, somos expertos en encontrar argumentos distractores.


Respuestas: Linux y Spaces.

¿La pelea…?

¡Promesas vs. Async/Await!

Espera, ¿esto es una pelea? ¿Tiene que ser así? ¿Ya no hablamos de callbacks?

No, no es una pelea. En última instancia, es otra herramienta potencial en tu caja de herramientas. Sin embargo, dado que async/await no reemplaza toda la funcionalidad de Promise (específicamente Promise.all, .race), resulta engañoso presentarlo como un sustituto.

Hay mucha gente influyente que promueve esta idea errónea de que async/await es el reemplazo de las Promesas que todos han estado esperando.

Pista: No, de ninguna manera, ni aunque sea un poco.

Una incorporación reciente a VS Code refuerza este sesgo. Como tuiteó @umaar:

Visual Studio Code can now convert your long chains of Promise.then()‘s into async/await! 🎊 Works very well in both JavaScript and TypeScript files. .catch() is also correctly converted to try/catch ✅ pic.x.com/xb39Lsp84V

— Umar Hansa (@umaar) September 28, 2018

Si odias las Promesas y quieres esta función de refactorización, no te culpo.


Entiendo. Lo comprendo.


He estado allí. 🤗


Solía odiar las Promesas. Hoy he cambiado de opinión por completo. Las Promesas son increíbles. Pueden habilitar/fomentar que aproveches la composición de funciones.

Hay 2 áreas en las que recomiendo enfocarte primero para mejorar tu técnica con Promesas.

  1. Funciones con nombre (no anónimas)
  2. Funciones de un solo propósito

#1: ¡Funciones con nombre!

Elimina tus métodos anónimos. Usar funciones con nombre hace que el código se lea como poesía de tus requisitos.

Veamos un ejemplo típico:

Realizando una solicitud HTTP GET con fetch:

Anti‑Patrón

// ❌ Usando funciones anónimas en línea 💩
fetch(url)
.then(response => response.status < 400
? response
: Promise.reject(new Error('Request Failed: ' + response.ststus)))
.then(response => response.text())

Solución: Métodos con nombre

// ✅ Aparece claridad: funciones con nombre
fetch(url)
.then(checkResponse)
.then(getText)
// Funciones reutilizables de propósito general
function checkResponse(response) {
return response.status < 400
? response
: Promise.reject(new Error('Request Failed: ' + response.ststus))
}
function getText(response) {
return response.text()
}

Los beneficios de este enfoque se hacen cada vez más evidentes a medida que tu código se vuelve más DRY.

Recursos adicionales: Mira mis videos de 1 minuto sobre registro básico y depuración avanzada usando esta técnica.

#2: Propósito único (Funciones)

Suena engañosa y precisa: Propósito único.

Sin embargo, es tan subjetivo, arbitrario y, a veces, incluso carente de sentido.

// 1 punto: el return y el ternario son efectivamente una sola línea
function checkResponse(response) {
return response.status < 400
? response
: Promise.reject(new Error('Request Failed: ' + response.ststus))
}
// 1 punto: el return y la expresión también son efectivamente una sola línea
function getText(response) {
return response.text()
}

Dada la implementación de una función, suma 1 punto por cada línea que contenga cualquiera de los siguientes tokens: if, return, ternario, for, const, let, var, switch, while, [].map/filter/reduce/etc. Añade 1 punto por cada instrucción (ignora líneas en blanco). Una cadena de expresiones o métodos encadenados cuenta solo como 1 punto.

Uf, eso fue un poco de jerga.
*/}

Curiosamente, la mayoría de los desarrolladores afirman que son bastante buenos aplicando Propósito Único a su código. No es casualidad: también se consideran excelentes conductores.

Veamos un ejemplo que el (increíblemente talentoso) Jake Archibald incluye en su artículo sobre async/await para el sitio de Google Developers (nota: 2024, enlace eliminado).

// source: https://developers.google.com/web/fundamentals/primers/async-functions
function logInOrder(urls) {
// fetch all the URLs
const textPromises = urls.map(url => {
return fetch(url).then(response => response.text());
});
// log them in order
textPromises.reduce((chain, textPromise) => {
return chain.then(() => textPromise)
.then(text => console.log(text));
}, Promise.resolve());
}

¿Propósito único?

Yo diría que no. ¿Qué está haciendo logInOrder?

  1. recorre una lista de urls
  2. las pasa a una llamada HTTP GET en línea:
    1. fetch HTTP
    2. devuelve el cuerpo de texto de la respuesta
  3. agrega un .then(text => console.log(text)) después de cada promesa en textPromise
    1. imprime los resultados de forma secuencial

Hay 5 métodos anónimos definidos dentro de esta única función. Como señala Jake, el .reduce es demasiado complejo. No tiene sentido escribir a mano mecanismos matizados por todo el código. En otras palabras, no escribimos código de creación de DOM con infinitos document.createElement(), element.setAttribute(), etc. En su lugar elegimos la mejor herramienta entre muchas opciones: funciones auxiliares/utilitarias, bibliotecas o frameworks.

Solución: Funciones de Propósito Único

Comienza extrayendo métodos

VS Code refactor extrayendo métodos async del código Promise

Continúa sustituyendo el .reduce() y logPromise() por un Promise.all y un ..map()

Cadena Promise refactorizada usando Promise.all y map para mayor legibilidad

Resumen

¡Prueba aplicar estas técnicas a tu propio código! Luego tuiteame y cuéntame cómo te fue. O si tienes preguntas o comentarios, también puedes contactarme.

Ayuda a difundir la #PromiseTruth y comparte este artículo. ❤️

credit: matt-nelson-414464-unsplash.webp

Lecturas relacionadas