Hace un par de proyectos atrás, me tocó refactorizar una aplicación hecha en ionic v3, la versión novedosa (En aquel tiempo) en utilizar un webview para generar una app móvil con angular y typescript al nivel más arriba.
Parte del motivo de la refactorización y reconstrucción de algunos componentes, era que algunos no funcionaban como se esperaba, errores básicos por falta de pruebas de parte de las personas que mantenían el proyecto mucho antes de mi equipo.
Como suele ser costumbre ya en proyectos fallidos, había cero documentación, en estos casos suelo analizar partes de código del proyecto para entender la lógica que se escribió y se intentó “bajar” en los requerimientos del cliente, mi sorpresa fue la siguiente al ver demasiado código de este tipo:
function subir(Data) {
console.log("estoy subiendo");
return new Promise((resolv, reject) => {
console.log("algun console log que no aporta nada ");
this.fService.subirFDeData(Data).then(
subidas => {
this.pService.subirPDeData(Data).then(
subidas => {
this.NService.subirSDeData(Data).then(
subidas => {
this.PService.subirPerDeData(Data).then(
subidas => {
this.IService.subirIdenDeData(Data).then(
subidas => {
this.subirPAndB(Data).then(
subidas => {
var filters = [
"Ref = '" + Data.Ref + "'"
];
this.Storage.query(new RScheme(), filters).then(
(Dataoff: any) => {
console.log("otro algun console log que no aporta nada");
let g = new Data(Dataoff[0]);
console.log("otro algun console log que no aporta nada que manda un JSON.stringify");
if (Dataoff[0]["IniciaGui"] == 1) {
console.log("otro algun console log que no aporta nada");
this.abrirGui(g).then(
subidas => {
if (Dataoff[0]["TerminaGui"] == 1) {
this.cerrarGui(g).then(
subidas => {
resolv("todo actualizado");
}, error => {
reject(error)
}
);
} else {
resolv("todo actualizado");
}
}, error => {
reject(error)
}
);
} else {
if (Dataoff[0]["TerminaGui"] == 1) {
console.log("otro algun console log que no aporta nada");
this.cerrarGui(g).then(
subidas => {
resolv("todo actualizado");
}, error => {
reject(error)
}
);
} else {
resolv("todo actualizado");
}
}
}, error => {
reject(error);
}
);
}, error => reject(error)
);
}, error => reject(error)
);
}, error => reject(error)
);
}, error => reject(error)
);
}, error => reject(error)
);
},
error => reject(error)
);
});
}
Este es un código completamente “REAL” con algunas modificaciones de nombres para no comprometer a la persona que lo hizo 😂
Era el plato fuerte de estos componentes bugeados, en corto identifique el problema, que más que veamos cajas negras intentando resolver de otra caja negra, era el acumulamiento de promesas llamada callback hell y es el resultado en los que se tiene lógica demasiado compleja para ciertas acciones de un negocio, en conjunto con una mala programación.
A pesar que fue un proceso de trabajo fuerte en conectar y hacer funcionar correctamente los service providers, parte del trabajo fue hacer más entendible y mantenible ese código de promesas acumuladas.
Solución
La mejor manera para resolverlo a mi gusto es con async/await por ejemplo:
async function subir(Data) {
try {
await this.fService.subirFDeData(Data);
await this.pService.subirPDeData(Data);
await this.fService.subirSDeData(Data);
// ... todos los métodos que quieras esperar
} catch (error) {
// notifica el error
}
}
No suelo ser fan de try/catch porque ese manejo de errores es “excepcional” y no debería suceder comúnmente errores aquí (Te recomiendo veas una manera de eliminar el uso de try/catch aquí). Otra solución podría ser utilizar el promise all, pero en el caso práctico del ejemplo no aplica por que se debe esperar a que una finalice para continuar con la otra, promise all las ejecuta de manera asíncrona, como quiera lo comento por que en algunos casos fue de utilidad.
async function subir(Data) {
// retornamos los services dentro de una arrow function
const p1 = () => this.fService.subirFDeData(Data);
const p2 = () => this.fService.subirPDeData(Data);
const p3 = () => this.fService.subirSDeData(Data);
// … puedes incluir todos las promesas o async functions que
// quieras en el iterable pasado como argumento
Promise.all([p1, p2, p3]).then((data) => {
// ok
}, (fail) => {
// notifica el error
});
}
Conclusión
Los negocios y la lógica que suelen ser complejos requieren un análisis profundo pero no por eso, un mal código.