6

Given the the following code:

var fs = require('fs');

var asyncErrorPromise = new Promise(function(resolve) {
  fs.readFile('does/not/exist.blah', function(fileError, content) {
    if (fileError) throw fileError;
    resolve(content);
  });
});

asyncErrorPromise
  .then(function(content) {
    console.log('content received: ' + content);
  })
  .catch(function(err) {
    console.log('error received: ' + err.name);
  });

I would expect that the fileError that is thrown (because the file I try to load does not exist) will be caught by the Promise and funneled into the .catch() statement. So in the end I'd have this output: error received: ENOENT.

However, when I run this code I get the following instead:

    if (err) throw err;
             ^

Error: ENOENT: no such file or directory, open 'does/not/exist.blah'
    at Error (native)

So the error was not caught: it threw just as it normally would, as though it were outside a Promise.

Why is this happening?

Everything I can find to read about Promises is more concerned with errors being silently swallowed than with this problem, where it actually throws when you don't want it to!

And how should I achieve my intent here? Do I need to use try-catch statements and the reject handler?

davidtheclark
  • 4,666
  • 6
  • 32
  • 42
  • As you mentioned you need to catch and reject/defer the promise. Any uncaught exception will be handled by the engine and will halt the script. – Baski Nov 11 '15 at 13:18
  • why would you `catch` the error? simply reject it – Jaromanda X Nov 11 '15 at 13:19
  • 1
    What promise lib are you using? In ES6, errors thrown inside a new promise constructor should implicitly reject the promise, so your code should work assuming the promise implementation is ES6 compat. – ehynds Nov 11 '15 at 13:19
  • `errors thrown inside a new promise constructor should implicitly reject the promise` - node native ES6 promise doesn't seem to play well with others – Jaromanda X Nov 11 '15 at 13:22
  • 1
    Probably because the error is thrown inside of an async callback. If you throw outside of that as a test, it'll probably work. I'd follow the advice of the other commenters and simply reject the error. – ehynds Nov 11 '15 at 13:23
  • @ehynds- you are correct – Jaromanda X Nov 11 '15 at 13:26
  • As a side note, one should never `throw` in custom async callback functions but instead reject the promise. – Daniel B Nov 11 '15 at 13:36
  • @DanielB: That's not really a side note: that's kind of the whole question. Is that a feature of Promises or a bug? Is the above *supposed* to happen? Why? – davidtheclark Nov 11 '15 at 13:40
  • 2
    The behaviour **is** expected, and it's not a bug. Since it's async, you'll loose the context of the error in the `process.on('uncaughtException')` handler. To keep the context you'd have to use something like [Domain](https://nodejs.org/api/domain.html) where a `Domain` object handles the error in a handler. – Daniel B Nov 11 '15 at 13:55
  • That's the answer I was looking for, thanks! If you want to enter it as an answer instead of a comment I'll accept it. – davidtheclark Nov 11 '15 at 14:19

1 Answers1

4

You cannot catch an error thrown in an asynchronous callback function using promises, since its context will be lost. In your case with Node, you will loose the context of the error in the process.on('uncaughtException') handler, and the callback will run as its own event in the eventqueue, with its own context and scope.

Using promises, the correct solution will be to reject the wrapping promise.

An alternative would be to use a Domain object (Domain documentation), but it's pending deprecation. With a domain you can maintain the context and register a handler for your errors, as below.

var domain = require('domain');

var d = domain.create();

d.on('error', function(err){
    //Do something with your error
});

d.run(function(){
    //Run your async code that might throw error
});
Daniel B
  • 8,770
  • 5
  • 43
  • 76