Background
I am using Promises, and I have a multitude of functions that may or may not return a Promise and that may or may not fail, like in the example below:
//does not return a Promise, simply a string
let goodFun = function(){
return "I like bananas!";
};
//blows up!
let badFun = function(){
throw "A general error blaahh!";
};
//You get the point ...
Since these functions may or may not return Promises and may or may not fail, I need to wait for all of them to execute. To achieve this I have a function to call them all and wait for their execution:
let asyncFun = function(){
return Promise.all([badFun(), goodFun()]);
};
Problem
So far so good. My code calls asyncFun
and I expect some of its functions to actually fail. To be prepared, I added a catch:
let executor = function(){
let numbsArray = [1, 2, 3];
let respArray = [];
for(let num of numbsArray){
respArray.push(
asyncFun()
.catch( error => console.log(`I failed with ${error} and ${num}`))
);
}
return Promise.all(respArray);
};
The problem is that catch
is not catching anything at all!
Even adding a catch to the function calling the executor
is not catching anything !
executor()
.catch(error => console.log("Failed miserably to catch error!"));
Research
I don't really understand why my catch clauses are not catching the exception. To find out, I read this discussion:
- How to make catched promises fail in jest
Which leads me to believe that all my functions goodFun
and badFun
, no matter what, must always return a promise.
This is confusing for me, because according to the MDN documentation the array may contain a Promise, or a result from one (like a string, or a number).
I would also like to avoid adding even more boiler plate code to my functions ....
Question:
- How do I fix my code, so the catches work adding a minimum or boilerplate code?
Code
let goodFun = function() {
return "I like bananas!";
};
let badFun = function() {
throw "A general error blaahh!";
};
let asyncFun = function() {
return Promise.all([badFun(), goodFun()]);
};
let executor = function() {
let numbsArray = [1, 2, 3];
let respArray = [];
for (let num of numbsArray) {
respArray.push(
asyncFun()
.catch(error => console.log(`I failed with ${error} and ${num}`))
);
}
return Promise.all(respArray);
};
executor()
.catch(error => console.log("Failed miserably to catch error!"));
The reason your .catch
es are not working is that the errors are being thrown synchronously. The code execution never even executes that .catch() instruction to set up a catch handler because the error has already been thrown and the code execution has gone elsewhere. If you wrapped everything in an ordinary try-catch
, I think you'd see it catching the errors you're throwing.
Example:
let goodFun = function() {
return "I like bananas!";
};
//blows up!
let badFun = function() {
throw "A general error blaahh!";
};
try {
Promise.all([goodFun(), badFun()])
.then(results => console.log(results))
.catch(error => console.error(error))
} catch (e) {
console.error("Wups, I caught an error. This wasn't supposed to happen.", e);
}
This is confusing for me, because according to the MDN documentation the array may contain a Promise, or a result from one (like a string, or a number).
The MDN documentation is absolutely correct, but if badFun()
throws an error, there's nothing that Promise.all
can do about it, because the the program flow will be flying down the call stack looking for something to catch the error that was thrown.
If you're not sure whether some code is synchronous or promise-based (or if it will throw a synchronous error), you can use this to safely wrap it a promise:
function executeSafe(action) {
return Promise.resolve().then(action);
}
This will shield action() from any "boom" that takes place.
Example:
let executeSafe =
action => Promise.resolve().then(action);
let goodFun =
() => "I like bananas!";
//blows up!
let badFun =
() => { throw "A general error blaahh!" };
Promise.all([goodFun, badFun].map(executeSafe))
.then(results => console.log(results))
.catch(error => console.error(error))
FYI, if you're using Bluebird, then it has a built-in method Promise.try
, that serves the same purpose as executeSafe
above: Promise.all([goodFun, badFun].map(Promise.try))
.
Here's your full example with the needed modifications:
let executeSafe =
action => Promise.resolve().then(action);
let goodFun =
() => "I like bananas!";
//blows up!
let badFun =
() => {
throw "A general error blaahh!"
};
let asyncFun =
() => Promise.all([goodFun, badFun].map(executeSafe));
let executor = function() {
let numbsArray = [1, 2, 3];
return Promise.all(numbsArray.map(num =>
asyncFun()
.catch(error => console.log(`I failed with ${error} and ${num}`))
));
}
executor();
And here it is with the .catch
outside of the executor
:
let executeSafe =
action => Promise.resolve().then(action);
let goodFun =
() => "I like bananas!";
//blows up!
let badFun =
() => {
throw "A general error blaahh!"
};
let asyncFun =
() => Promise.all([goodFun, badFun].map(executeSafe));
let executor = function() {
let numbsArray = [1, 2, 3];
return Promise.all(numbsArray.map(asyncFun));
}
executor().catch(error => console.error("Miserably failed to catch anything.", error));