Aprendiendo de los errores de 7 líneas de código en JavaScript/Node.JS

Hace unos días, mientras buscaba código de ejemplo en un archivo extenso, me topé con 7 líneas de código que resumen el patrón de todo el código en el archivo que estaba leyendo en Node.JS.

class AlgunaClase {
...
async store(data) {
return db.MyModel.create(data).then(created => {
return created.toJSON();
}).catch(error => {
throw error;
});
}
...
}

Para iniciar, esta sección de código es un método de una clase en JS que funciona perfectamente. Sin embargo, la pregunta latente es: ¿Si funciona perfectamente bien, cúal es el problema?

Si no logras encontrar por lo menos 3 malos usos del lenguaje, espero que este artículo te sirva para identificar lo que necesitas para mejorar tus habilidades de programación. Así que empezemos a ver punto por punto.

Objeto this

La programación orientada a objetos (OOP) suele usar la palabra reservada this para que un objeto pueda referirse a sí mismo. En JS, los objetos son definidos con funciones y, en versiones recientes del estandar, se pueden definir mediante el instanciamiento de clases cuyo código interno hace uso de esta palabra reservada para acceder a sus atributos y métodos.

Sin embargo, si revisamos nuestro código de ejemplo, vemos que dicho método no hace uso de this , por lo que no hay necesidad de que sea parte de una clase, por ende, bajo este criterio, esta debería escribirse de la siguiente manera fuera de una clase.

async function store(data) {
return db.MyModel.create(data).then(created => {
return created.toJSON();
}).catch(error => {
throw error;
});
}

Arrow functions

Las Arrow functions son funciones en línea pensadas principalmente para ser usadas como funciones callback o como funciones que no van a generar objetos.

Cuando la función tiene muchas líneas de código, esta tiene la siguiente forma:

(var1,var2,..) => {
sentencia 1
sentencia 2
...
return algúnValor
}

Otro caso es cuando solo se hace una sentencia sin retornar valor:

// Ejemplo
(obj) => { obj.val = 3 }
// O, debido a que solo tiene un solo argumento,
// los paréntesis se pueden omitir
obj => { obj.val = 3 }

No obstante, si solo se desea retornar un valor, la situación es diferente.

// Mal código
(a,b) => {
return a + b
}
// Buen código
(a,b) => a + b

Habiendo explicado lo anterior, el código en cuestión se puede reescribir de la siguiente manera:

const store = async data =>
db.MyModel.create(data)
.then(created => created.toJSON())
.catch(error => { throw error; });

Error Handling

El manejo de errores requiere un artículo (que veremos en un futuro no muy lejano) para ahondar en el tema, empero, cabe resaltar que se capturan errores (con catch ) para hacer alguna operación de valor con el error:

  • Hacer log o reportes.
  • Volver a lanzar un error que describa de manera más legible el error de un bloque de código (no solo el de la línea dónde se originó).
  • Evitar romper el flujo y/o adoptar otro que nos convenga.

Dicho esto, no tiene ningún sentido capturar el error para solo relanzarlo sin que haya alguna lógica adicional, por ende, hay que eliminar el catch innecesario.

const store = async data =>
db.MyModel.create(data)
.then(created => created.toJSON())

Asincronía

La asincronía es otro tema extenso que requerirá varios artículos. Pese a esto, hablaremos de manera introductoria de async/await y Promise (en otros lenguajes conocidos como Future). Por el momento, les recomiendo leer la documentación de Mozilla Foundation.

Las promesas son objetos que nos permiten poder ejecutar código mientras se espera la respuesta de procesos externos a nuestro código con larga duración computacional. Hay que recordar que esta escala de tiempo es relativa al procesador, en dónde 50 ms de espera ya es una eternidad de tiempo perdido.

Volviendo al tema, los procesos que pueden tomar mucho tiempo son las respuestas de consultas HTTP, la lectura de archivos, entre otras tareas de tipo entrada y salida (I/O, Input/Output).

Tradicionalmente, estos procesos se manejan con Promesas, sin embargo, los nuevos estándares de JS permiten usar la sintaxis de async/await para hacer código más legible.

/* Ejemplo 1 */
function leerDatosYMostrar() {
let data = 'data inicial'
fetch("https://tuservicio.com/leerdatos")
.then(response => response.text())
.then(text => {
data = text // supongamos que text="data del servicio"
console.log(data)
})
console.log(data)
}
/* Ejemplo 2
* Parecido al Ejemplo 1, pero las diferencias
* sustanciales las veremos en otro artículo
*/
function leerDatosYMostrar() {
let data = 'data inicial'
const leerDatos = async () => {
let response = await fetch("https://tuservicio.com/leerdatos")
let data= await response.text()
console.log(data) // supongamos que text="data del servicio"
}
leerDatos()
console.log(data)
}

Para ambos casos, la salida es la siguiente:

data inicial
data del servicio

Esto, a primera instancia, nos puede parecer contraintuitivo, pues primero ejecutamos la sección de leer datos que debe imprimir primero data del servicio. Pero eso es cuando usamos código secuencial, no cuando usamos código asíncrono.

En el código asíncrono, hay que identificar qué proceso toma más tiempo. Si vemos bien, la función fetch toma varios milisegundos, por lo que no se ejecutara el procesamiento de la respuesta hasta que se retorne una. Es así que la asincronía entra a jugar, pues nos permite seguir ejecutando código mientras se da la espera, es decir, ejecurará el console.log(data) que imprime data inicial mientras espera la respuesta del servidor, y cuando esta llegue, recién la procesará y la imprimirá.

Notar que la sintaxis async/await solo tiene sentido cuando se usa async para declarar a una función como asíncrona (internamente la función va a retornar una promesa del valor que retornemos) y await sirve para indicar dónde esperar la respuesta de una promesa.

Pese a lo tedioso de esta explicación, el código puede ser reescrito de dos formas válidas, donde la forma de código dependerá del criterio de cada programador.

/* Forma 1 */
const store = data =>
db.MyModel.create(data)
.then(created => created.toJSON())
/* Forma 2 */
const store = async data => {
let created = await db.MyModel.create(data)
return created.toJSON()
}

Resumen

Cada lenguaje tiene sus detalles que lo hacen único por más que se parezcan otros. Es necesario tomarse un tiempo estudiando a detalle los por menores del lenguaje. Es muy posible que luego de haber aprendido más sobre el lenguaje, te des cuenta que debas reescribir tu código para que sea más limpio y más legible. Recuerda que no solo escribes código para ti, sino para los que contribuyan a tu proyecto; y cuando alguien más quiera empezar donde tú terminaste, esta persona tendrá una gran dificultad para iniciar, pues no sabrá si lo que hiciste está bien por alguna justificación técnica o si simplemente es mal código.