I've been studying a lot on asynchronous javascript, from callbacks to promises to async/await.
Now I'm having a hard time understanding why do I lose control over error handling in the following code sample. I've even dived into Node's source code, but that gave me more trouble than answers.
This is what I do:
- Inside a global try/catch block, I call Node's built-in fs.readFile, giving it a callback that just throws an error;
- The error isn't caught, however, and I suppose it somehow bubbles up to the global scope, provoking the JS Engine to emit an uncaughtException event.
const { readFile } = require('node:fs') process.on('uncaughtExceptionMonitor', (error, origin) => { console.log('\nuncaughtExceptionMonitor handler: {') console.log(`ERROR: ${error}`) console.log(`ORIGIN: ${origin}`) console.log('}\n') }) try { readFile('./jsconfig.json', 'utf8', (err, res) => { if (err) console.log('failed to read file') if (res) console.log('successfully read file') throw new Error(`THROWN BY BUILT-IN FUNCTION'S CALLBACK`) //error thrown }) } catch (error) { console.log(`error caught by readFile's outer scope`) // not caught here }
When run, it gives me the following result:
successfully read file
uncaughtExceptionMonitor handler: { ERROR: Error: THROWN BY BUILT-IN FUNCTION'S CALLBACK ORIGIN: uncaughtException }
C:\Users\Heavy\Documents\dev\hwoarang\leg.js:15 throw new Error(
THROWN BY BUILT-IN FUNCTION'S CALLBACK
) //error thrown ^Error: THROWN BY BUILT-IN FUNCTION'S CALLBACK at C:\Users\Heavy\Documents\dev\hwoarang\leg.js:15:11 at FSReqCallback.readFileAfterClose [as oncomplete] (node:internal/fs/read_file_context:68:3)
This is my first question so I'm not quite comfortable with Stackoverflow's formatting. I apologize for that.
Can you help me?
CodePudding user response:
readFile
is an asynchronous operation; so, it won't execute in the typical step-by-step (line-by-line) way. So, when the error is thrown, the synchronous code that you have will have been executed already.
Simplest solution here is to use synchronous readFileSync that will run within the same execution context.
If you want to have the synchronous looking code while still executing an asynchronous code, you can promisify the readFile
function and call that using async/await:
const readFilePromise (filename, format) = new Promise((resolve, reject) => {
readFile(filename, format, (err, res) => {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
// usage
(async () => {
try {
const res = await readFilePromise('./jsconfig.json', 'utf8');
console.log('successfully read the file', res);
} catch (err) {
console.log('failed to read file');
}
})();
CodePudding user response:
try...catch
applies to the code that runs synchronously in the try
block. The callback given to the readFile
function, does not execute synchronously, but after the try
block has completed execution (and all synchronous code that follows it until the call stack has been emptied and the job queue(s) are processed by the engine). Once execution completes the try
block, it can never happen to still get in the catch
block later.
Later, a new execution context is created where the callback is executed. This execution context knows nothing about this try..catch
block.
You can make use of the promise version of fs
:
const { promises: { readFile } } = require("fs");
async function test() {
try {
const res = await readFile('./jsconfig.json', 'utf8');
console.log('successfully read file');
throw new Error(`THROWN AFTER AWAIT`) //error thrown
} catch (error) {
console.log(`error caught`) // will be caught
}
}
test();
Here, with await
, the try
block is suspended together with the function's execution context. Once the awaited promise is resolved, that function context is restored with its unfinished try
block, and then execution continues within that block, so that an error will be caught and make the catch
block execute.