Javascript. Promises, todo lo que necesitas saber (III)
Cómo gestionar los errores durante la ejecución de Promises
Una vez que ya sabemos cómo ejecutar varias “Promises” encadenándolas mediante el método then
, ha llegado el momento de profundizar en la gestión de errores, algo que como ya vimos en el primer capítulo de esta serie podemos hacerlo mediante el método catch
.
En este tercer capítulo veremos cómo gestionar errores durante la ejecución de Promises y la forma en que son tratadas las excepciones que nosotros lancemos.
¡Vamos allá!
El método catch
Cuando creamos un objeto Promise
la función que recibe como argumento recibe a su vez dos funciones: resolve
y reject
:
const promise = new Promise((resolve, reject) => { ... });
Cuando invocamos el método reject
, la ejecución salta al controlador más cercano de esa Promise, el cual podemos definir mediante el método catch
:
const promise = new Promise(function(resolve, reject) {
setTimeout(() => reject('an error occurred'), 1000);
});
promise.catch(error => console.log(error)); // 'an error occurred'
Otro ejemplo podemos verlo cuando empleamos el método fetch
y se produce un error durante la llamada asíncrona:
fetch('https://wrongodmain.com')
.then(response => response.text())
.catch(error => console.log(error.message)); // 'Failed to fetch'
El método catch
controla además cualquier error sucedido durante las ejecución de varias “Promises” encadenadas:
fetch('https://api.com/me')
.then(response => response.json())
.then(user => fetch(`https://api.com/users/${user.id}/articles`))
.then(response => response.json())
.catch(error => console.log(error.message));
Gestión de errores
El método catch
nos permite gestionar los errores de modo que podamos:
- Volver a lanzar el error (o uno nuevo) para que sea gestionado por el siguiente
catch
(de haberlo). - Tratar ese error y permitir que el siguiente
then
sea ejecutado como si la “Promise” se hubiera resuelto correctamente.
El primer caso podemos verlo con el siguiente trozo de código:
fetch('https://wrongodmain.com')
.then(response => response.text())
.catch(error => { throw error; })
.catch(() => console.log('called'));
En él, el primer catch
es ejecutado, lanzando de nuevo el error que es capturado por el segundo catch
. Esto nos permite por ejemplo, tener distintos catch
que gestionen distintos tipos de errores del mismo modo que hacemos con las excepciones.
Además, también podemos gestionar el error de modo que el siguiente then
sí sea ejecutado. Para ello bastará con no lanzar una excepción dentro del método catch
, lo cual permitirá que el método then
sea invocado:
fetch('https://wrongodmain.com')
.then(response => response.json())
.catch(error => { return { id: 1, fullname: 'default user'};})
.then(user => console.log(user.fullname)); // default user
Reject sin catch
¿Qué sucede cuando invocamos reject
en una Promise pero no existe ninguna instrucción catch
para recoger el error? Del mismo modo que sucede con otro tipo de errores, el error de la “Promise” provocará que el “script” falle advirtiendo de ello en la consola por medio del error global: “Uncaught error”:
const promise = new Promise(function(resolve, reject) {
reject('an error occurred');
});
// Uncaught (in promise) an error occurred
En el navegador podemos gestionar este tipo de errores mediante el evento unhandledrejection
el cual nos permite acceder tanto a la “Promise” que provocó el error como al error mismo:
const promise = new Promise(function(resolve, reject) {
reject('an error occurred');
});window.addEventListener('unhandledrejection', function(event) {
console.log(event.promise);
console.log(event.reason);
});
Excepciones
Si durante la ejecución de una “Promise” lanzamos una excepción, esta también será recogida dentro del método catch
:
const promise = new Promise((resolve, reject) => {
throw new Error('An error occurred');
})
promise.catch(console.log); // An error occurred
Es decir, funciona del mismo modo que si estuviéramos usando reject
.
Por supuesto lo mismo sucede si dentro del método then
lanzamos una excepción:
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(function() { throw new Error('An error occurred');})
.catch(error => console.log(error.message)); //'An error occurred'
O si sucede cualquier otro error, como por ejemplo el mal uso de una variable:
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(function() { console.log(foo.bar); })
.catch(error => console.log(error.message)); // foo is not defined
Catch y setTimeout
Sin embargo, hay un caso en el que el método catch
no recogerá las excepciones que lancemos:
new Promise(function(resolve, reject){
setTimeout(() => {
throw new Error('error')
}, 1000)
}).catch(error => console.log('not called!'));// Prints in console: 'Uncaught Error: error'
Esto se debe a cómo funciona el Event Loop de Javascript y las macrotasks y microtasks, las cuales tienen pilas de ejecución distintas:
Puesto que la excepción que lanzamos dentro del callback de setTimeout
tiene lugar en la pila de su macrotask, ésta no puede ser capturada por el catch
de la Promise
, como sin embargo si sucede cuando invocamos el método reject
en el callback de setTimeout
:
new Promise(function(resolve, reject) {
setTimeout(() => reject('error'), 1000)
}).catch(error => console.log('called!'));
Referencia: https://stackoverflow.com/questions/48969591/why-promise-can-not-catch-the-error-throw-by-settimeout
Conclusiones
Como habéis podido ver, el método catch
de los objetos “Promise” nos permite gestionar los errores y las excepciones que sucedan durante la ejecución de la “Promise” de diversas maneras:
- Al final de una serie de promesas encadenadas nos permitirá capturar cualquier error sucedido durante su ejecución.
- En medio de una serie de promesas encadenadas nos permitirá tratar el error de modo que podamos permitir la ejecución de la cadena devolviendo un valor válido o lanzando un nuevo error.
- Desde
catch
podemos lanzar nuevos errores que sean capturados por sucesivoscatch
, lo cual nos permite gestionar distintos tipos de excepciones del mismo modo que hacemos con los bloquestry/catch
.
Además, en los navegadores contamos con el evento unhandledrejection para capturar los errores que hayan sucedido durante la ejecución de “Promises” que no contasen con un catch
.
¡Ahora ya no hay excusa para que nuestra aplicación se quede colgada durante las llamadas asícronas que realicemos por medio de “Promises”!
¿Quieres recibir más artículos como este?
Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇