Home > Back-end >  Can't catch error thrown from Node's built-in function callback
Can't catch error thrown from Node's built-in function callback

Time:06-17

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:

  1. Inside a global try/catch block, I call Node's built-in fs.readFile, giving it a callback that just throws an error;
  2. 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.

  • Related