I am trying to understand promise error handling for the cases of thrown errors and exceptions (code bugs) in the promise executor, comparing them to reject. This is for node.js.
It seems like there are clear differences in the handling of reject vs throw and exceptions.
Some web articles imply there is an implicit try/catch around promises that converts exceptions and throws to rejects, but apparently that is not true, or not always true.
I wrote some test code to study it. That code and the results are below.
Test code:
function delay(ms) {
return new Promise((resolve, reject) => {
setTimeout(value => {
resolve();
}, ms);
});
}
function f(delayMs, action) {
return new Promise((resolve, reject) => {
if (delayMs == 0) {
console.log(`f() forcing error type ${action} sync...`);
if (action == 'exception') {
console.xxx();
} else if (action == 'throw') {
throw new Error('thown error sync');
} else {
reject('reject sync');
}
} else {
delay(delayMs).
then(() => {
console.log(`f() forcing error type ${action} after delay...`);
if (action == 'exception') {
console.yyy();
} else if (action == 'throw') {
throw new Error('thrown error after delay');
} else {
reject('reject after delay');
}
});
}
});
}
function main(delayMs, action) {
f(delayMs, action).
then(value => {
console.log('Should not see this');
}).
catch(err => {
if (typeof err == 'object') console.log('In main catch:', err.message);
else console.log('In main catch:', err);
}).
finally(() => {
console.log('In main finally');
});
}
main(0, 'exception');
main(0, 'throw');
main(0, 'reject');
main(500, 'reject');
main(1000, 'exception');
main(1500, 'throw');
Output:
f() forcing error type exception sync...
f() forcing error type throw sync...
f() forcing error type reject sync...
In main catch: console.xxx is not a function
In main catch: thown error sync
In main catch: reject sync
In main finally
In main finally
In main finally
f() forcing error type reject after delay...
In main catch: reject after delay
In main finally
f() forcing error type exception after delay...
/mnt/c/Users/test/promise-stackoverflow-2.js:25
console.yyy();
^
TypeError: console.yyy is not a function
at /mnt/c/Users/test/promise-stackoverflow-2.js:25:33
Node.js v18.12.1
For the synchronous case, all three error types are handled the same, being caught in the catch in the main function.
For the asynchronous case, which admittedly involves another Promise, the reject case is still handled in the catch in the main function. But the other two cases (exception and throw) cause a higher level exception and the process exits.
It looks like I can add a catch handler for the delay() promise, and then do a reject in that catch and it will propagate to the main catch handler. I didn't think I would need to do this - am I missing something?
Also, some articles imply that error handling is easier using async/await. To me, it seemed like if you want to do strict error handling, just using promises was easier.