186

Within node.js readFile() shows how to capture an error, however there is no comment for the readFileSync() function regarding error handling. As such, if I try to use readFileSync() when there is no file, I get the error Error: ENOENT, no such file or directory.

How do I capture the exception being thrown? The doco doesn't state what exceptions are thrown, so I don't know what exceptions I need to catch. I should note that I don't like generic 'catch every single possible exception' style of try/catch statements. In this case I wish to catch the specific exception that occurs when the file doesn't exist and I attempt to perform the readFileSync.

Please note that I'm performing sync functions only on start up before serving connection attempts, so comments that I shouldn't be using sync functions are not required :-)

vishal_g
  • 3,871
  • 4
  • 21
  • 34
Metalskin
  • 3,998
  • 5
  • 37
  • 61

6 Answers6

279

Basically, fs.readFileSync throws an error when a file is not found. This error is from the Error prototype and thrown using throw, hence the only way to catch is with a try / catch block:

var fileContents;
try {
  fileContents = fs.readFileSync('foo.bar');
} catch (err) {
  // Here you get the error when the file was not found,
  // but you also get any other error
}

Unfortunately you can not detect which error has been thrown just by looking at its prototype chain:

if (err instanceof Error)

is the best you can do, and this will be true for most (if not all) errors. Hence I'd suggest you go with the code property and check its value:

if (err.code === 'ENOENT') {
  console.log('File not found!');
} else {
  throw err;
}

This way, you deal only with this specific error and re-throw all other errors.

Alternatively, you can also access the error's message property to verify the detailed error message, which in this case is:

ENOENT, no such file or directory 'foo.bar'
starball
  • 20,030
  • 7
  • 43
  • 238
Golo Roden
  • 140,679
  • 96
  • 298
  • 425
  • 1
    Thanks, that's the info I was looking for. I just presumed that it would be a specific type of Error. I have also just realised I misunderstood how the try/catch works, I was thinking that you could catch a specific error type (a la java). Thanks for the info Golo. :-) – Metalskin Jan 18 '13 at 04:29
  • 5
    Also `EACCES` code should be checked in the if statement for the case when the file is there but cannot be read due to lack of permissions – Gergely Toth Jul 25 '16 at 13:55
36

I prefer this way of handling this. You can check if the file exists synchronously:

var file = 'info.json';
var content = '';

// Check that the file exists locally
if(!fs.existsSync(file)) {
  console.log("File not found");
}

// The file *does* exist
else {
  // Read the file and do anything you want
  content = fs.readFileSync(file, 'utf-8');
}

Note: if your program also deletes files, this has a race condition as noted in the comments. If however you only write or overwrite files, without deleting them, then this is totally fine.

Francisco Presencia
  • 8,732
  • 6
  • 46
  • 90
  • 2
    now, fs.existsSync is [not deprecated anymore](https://nodejs.org/api/fs.html#fs_fs_existssync_path): "Note that fs.exists() is deprecated, but fs.existsSync() is not." – falkodev Feb 17 '17 at 11:43
  • 34
    Not better at all. What if the file is removed from disk between the existsSync and readFileSync call? Your code now has a race condition built in waiting to happen... – tkarls Jun 30 '17 at 08:45
  • 3
    @tkarls yes that is totally right, that was written in 2015 when I was still learning Node.js and it has a race condition. However, two things to note: the likehood of this race condition is so minimal that it can basically be ignored, and the second and superseeding the first is that I would be using try/catch with async/await nowadays making my code more flexible to "other" exceptions (since Node is exception-friendly). – Francisco Presencia Jul 01 '17 at 18:38
  • 4
    Race conditions don't matter until they do. Answers like this are why software is so bug-ridden, why you need to restart your computer so often, why there are so many security vulnerabilities, etc., etc. Stack Overflow should have a flag for potentially harmful answers. – Jonathan Tran Apr 06 '20 at 14:43
  • Another comment N years later, after learning a bunch more (added a note in the answer). This is totally fine in the context of a write-only filesystem program, but as noted if the files can also be removed this has a race condition and it's not code I'd be writing nowadays (specially because of that Sync!). I've also authored the package [`files`](https://www.npmjs.com/package/files) with all I've been learning to make the async and try/catch easier. – Francisco Presencia Jun 12 '20 at 16:42
  • 3
    I wann chime in here and agree with both people involved. But disagree with @tkarls black and white description. For example, I am writing code for parsing markdown files on disk, that will never be removed. In this case then checking it exists makes a lot more sense than using try catch as a control flow when I know there will never be this race condition. Software engineering is not a right and wrong is all circumstances kind of thing... – sethreidnz Feb 13 '21 at 22:25
  • More can go wrong than "the file was quickly removed after checking for existence and before reading"! File ownership, file permissions, filesystem problems, etc. Best to just do what you want to do and catch the exception: `try{contents=fs.readFileSync(file);}catch(e){...log and maybe even check for existence here for a prettier error message...}`. – lmat - Reinstate Monica Feb 23 '22 at 19:22
  • TypeError: fs.existsSync is not a function – AlxVallejo Dec 21 '22 at 21:20
20

You have to catch the error and then check what type of error it is.

try {
  var data = fs.readFileSync(...)
} catch (err) {
  // If the type is not what you want, then just throw the error again.
  if (err.code !== 'ENOENT') throw err;

  // Handle a file-not-found error
}
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
6

I use an immediately invoked lambda for these scenarios:

const config = (() => {
  try {
    return JSON.parse(fs.readFileSync('config.json'));
  } catch (error) {
    return {};
  }
})();

async version:

const config = await (async () => {
  try {
    return JSON.parse(await fs.readFileAsync('config.json'));
  } catch (error) {
    return {};
  }
})();
sdgfsdh
  • 33,689
  • 26
  • 132
  • 245
  • You may want to add to your post that your solution is for ECMAScript 6. As of 01/01/18 there is no support from IE with approx 77% coverage of browser usage (https://caniuse.com/#feat=arrow-functions). I'm curious, how do you cater for IE users? – Metalskin Jan 03 '18 at 01:03
  • 2
    @Metalskin Webpack + Babel. However, `fs` is a Node module – sdgfsdh Jan 03 '18 at 09:26
  • Ahh, I'm out of touch with node, I suspect that node didn't support ES6 when I asked the question (could be wrong). Kinda forgot this was a node question as well ;-) – Metalskin Jan 04 '18 at 14:24
  • updating this... `fs.readFileAsync()` is now `fs.readFile()` and also shouldn't put the async function inside of a try/catch in node.js. the try/catch will never get the error since it's async. instead pass the error in the callback and handle it there: ```fs.readFile('/etc/passwd', (err, data) => { if (err) throw err; console.log(data); });``` from: https://nodejs.org/dist/latest-v12.x/docs/api/fs.html#fs_fs_readfile_path_options_callback – K.H. B Mar 30 '20 at 16:44
  • I believe that the try-catch will be called if the promise is rejected and you are awaiting the promise. – sdgfsdh Mar 30 '20 at 16:59
2

The JavaScript try…catch mechanism cannot be used to intercept errors generated by asynchronous APIs. A common mistake for beginners is to try to use throw inside an error-first callback:

// THIS WILL NOT WORK:
const fs = require('fs');

try {
  fs.readFile('/some/file/that/does-not-exist', (err, data) => {
    // Mistaken assumption: throwing here...
    if (err) {
      throw err;
    }
  });
} catch (err) {
  // This will not catch the throw!
  console.error(err);
}

This will not work because the callback function passed to fs.readFile() is called asynchronously. By the time the callback has been called, the surrounding code, including the try…catch block, will have already exited. Throwing an error inside the callback can crash the Node.js process in most cases. If domains are enabled, or a handler has been registered with process.on('uncaughtException'), such errors can be intercepted.

reference: https://nodejs.org/api/errors.html

ViktorKrpn
  • 49
  • 3
-1

Try using Async instead to avoid blocking the only thread you have with NodeJS. Check this example:

const util = require('util');
const fs = require('fs');
const path = require('path');
const readFileAsync = util.promisify(fs.readFile);

const readContentFile = async (filePath) => {
  // Eureka, you are using good code practices here!
  const content = await readFileAsync(path.join(__dirname, filePath), {
    encoding: 'utf8'
  })
  return content;
}

Later can use this async function with try/catch from any other function:

const anyOtherFun = async () => {
  try {
    const fileContent = await readContentFile('my-file.txt');
  } catch (err) {
    // Here you get the error when the file was not found,
    // but you also get any other error
  }
}

Happy Coding!

jdnichollsc
  • 1,520
  • 1
  • 24
  • 44
  • `Please note that I'm performing sync functions only on start up before serving connection attempts, so comments that I shouldn't be using sync functions are not required :-) ` ... – Will59 Apr 18 '21 at 13:44
  • So take care of poor performance with that kind of initialization logic, Observables/RxJS is a good alternative to wait for an initialization without having to block the process with sync functions – jdnichollsc Apr 18 '21 at 15:48