I have a problem with an exception, the thing is that I can't catch it and I don't know how to do it:
I have a model that does an insert into a database:
xxxModel.generaNuevoxxx = async function (c, tipo, xxx) {
let sql = `insert into xxx
(xxx)
values
(xxx)`;
let obj = {
xxx: xxx.xxx,
xxx: xxx.xxx,
}
try {
console.log(obj);
return await c.execute(sql, obj, dbController.config);
} catch (e) {
throw new Error(`(xxx-model) generaNuevoxxxx -> ${e.message}`);
}
}
Which if an exception occurs in the execution of the insert would throw the exception. Then I have a controller, which is somewhat more extensive:
async function fabricar(req, res) {
let c;
try {
/*Aqui se hacen más cosas*/
infoComponentes.map(async xxx => {
try{
let medidaxxx = calcularMedida(xxx);
xxx.s1 = medidaxxx.stock_1;
xxx.s2 = medidaxxx.stock_2;
if (xxx.FAMILIA == 'xxxx' || xxx.FAMILIA == '<<<<') {
await xxxModel.generaMovimientoxxx(c, "qwe", [componente]);
await xxxModel.generaNuevoxxx(c, "qwe", xxx);
////AQUI ES DONDE LLAMO AL MODELO, EL CUAL AL DEVOLVER LA EXCEPCIÓN NO LA MANEJA
}
}catch(e){
throw e;
}
});
await c.commit();
console.log("Desconectando...".red);
return res.status(200).send({ message: "Proceso acabado." });
}catch(e){
c.rollback();
console.log(`Error: (xxx) -> ${e.message}`);
return res.status(500).send({ message: e.message });
}
It tells me that the exception is unhandled and also arrives at the commit and finishes showing the finished process message. How could I catch that exception?
tl;dr
Short answer
Do not use a nested block to try to catch an error from an asynchronous function that you pass as an argument to an iterable(
map
) method, since the methodmap
is not asynchronous. Therefore the proper option is to handle promises with Promise.all() .Long answer with explanation and example code at the end
The problem is in the way you handle asynchronous functions. Although you are using
try...catch
, the procedure is not done the way you think.When you make the call to:
infoComponentes.map(async xxx => {...});
you are effectively calling an asynchronous function that is executed for each element of yourarray
, but themap
typeArray
method is not an asynchronous method.Let's look at an example:
As you can see, the method
map
is not executed asynchronously, that is, for each element ofnumbers
our function will be executedasyncDoubles
, but that function will be called immediately without waiting for the previous call to return a result.Let's see what happens now if we throw an error inside our async function based on the result of the operation. For that we will use
try...catch
.If we were to run such code in Node, we would get the following at the end of our code:
Why do we get this error?
Simply because all of our code has been executed, there is nothing else to process except what is in it
event loop
(our functionsetTimeout()
), which is executed after 2 seconds and in which a promise is rejected.However, there is no more code to process, the error propagates to the main process without a handler for it, and that is where it shows us the message.
How is it possible that
map
it is not async if I call it inside a function declared withasync
?In your code you call the method
map
inside an async functionasync function fabricar(req, res) {...}
- should it be async now?Let's see an example of behavior using
map
inside a functionasync
:Well, it's the same behavior.
However we have not used
await
. Should we use it?Well, nothing. We keep getting the same result. Resources are exhausted, so let's see the documentation for
async function
an answer: (own translation)And here's the whole mystery:
await
It also doesn't convert a synchronous function (likemap
) to a functionasíncrona
. What it doesawait
is wait for the function to be executed (map
) to return a result and then it continues with the execution of the functionasync
. The methodmap
doesn't return a Promise, so itawait
just pauses for a while, which is how long it takesmap
to call the functionasyncDoubles
, and continues on its way without waiting for calls toasyncDoubles
resolve the Promise.SOLUTION
We've already seen that using
async
to do what we're trying to do isn't an option, since the methodmap
isn't asynchronous.We must refactor our code. Currently based on what you state in your question, I can understand the process of what you are trying to accomplish.
I have given myself the task of creating an example as similar as possible so that it can be reproduced with the tools available here. The code would be the following:
Well, we have already reproduced the error, and we will see that when executing this code in Node, we will obtain the following error:
However, before obtaining this error, we can see the following in the console:
That is, the block
catch
that should catch our exception does not. And we already saw why earlier.Here comes the refactoring, having understood a little what you intend with your code.
The process I think you want to perform would be the following:
commit
on the database.In a nutshell you are performing a
bulk insert
on your database.Since the method
map
is not asynchronous, we must look for an alternative to insert the records in the database.An alternative, in order not to alter so much the way we are trying to do things, is to use Promise.all .
Basically, this method returns a Promise, which resolves successfully if all promises in the iterable argument complete successfully, and is rejected if any promises in the iterable argument are rejected.
The implementation would be the following:
Now, we're done
bulk insert
using promises and asynchronous programming.If we execute the previous code in Node the possible results would be:
Failed in Component 5
No component error but commit error
No component error and no commit error
I hope this answer clears your doubts and helps you solve the problem.
When you execute the method
.map(...)
, you are passing it an asynchronous function. That is, it is a function that returns a promise. It is similar (not the same) to using asetTimeout
:El error no se captura porque durante el
try - catch
no se ejecutaa()
, simplemente se declara ( o se delega, como prefieras verlo) que se ejecutará en un futuro próximo.Usando promesas sería algo así:
Tenemos dos escenarios: se ha lanzado un error desde dentro de la promesa (en
a1
) o el error fue reconocido y simplemente se llamó a función de rejection (ena2
). Al usar await, el código se queda esperando a la resolución de la promesa, pero si ésta lanza un error, no se puede capturar. Si llama a reject, se interpreta como un error asíncrono que se puede tratar dentro de la función asíncronaPor tanto podemos ver que si se manejan bien las promesas y las excepciones, podemos capturar los errores.
¿Qué ocurre con tu código? Tienes un array de promesas que no gestionas de ningún modo, con lo que los errores no se pueden capturar. Es lo que hace map: crea un array con el mismo número de elementos que array recorrido, generados por la función que le has pasado.
La solución es usar Promise.all():