30

Can errors from a non-awaited async call be caught, sent to an original encapsulating try/catch, or raise an uncaught exception?

Here's an example of what I mean:

async function fn1() {
    console.log('executing fn1');
}

async function fn2() {
    console.log('executing fn2');
    throw new Error('from fn2');
}

async function test() {
    try {
        await fn1();
        fn2();
    }
    catch(e) {
        console.log('caught error inside test:', e);
    }
}

test();

In this scenario, the error thrown from fn2 will be swallowed silently, and definitely not caught by the original try/catch. I believe this is expected behavior, since fn2 is most likely being shoved off to the event loop to finish at some point in the future, and test doesn't care when it finishes (which is intentional).

Is there any way to ensure that errors are not accidentally swallowed by a structure like this, short of putting a try/catch internal to fn2 and doing something like emitting an error? I would even settle for an uncaught error without knowing how to catch it, I think -- I don't expect thrown errors to be typical program flow with what I am writing, but swallowing errors makes it relatively annoying to debug.

Side note, I'm using Babel to transpile the code using the babel-runtime transform, and executing it with node.

Martijn Pieters
  • 889,049
  • 245
  • 3,507
  • 2,997
dvlsg
  • 4,568
  • 1
  • 25
  • 34
  • Im not sure what are you trying to achieve, but would there be a reason not to use promises? – Tomas Jun 04 '15 at 17:07
  • 2
    Use a promise library that supports unhandled rejection detection. – Bergi Jun 04 '15 at 17:12
  • Tom, not really, no. I was intentionally using async/await over promises to see what could be done with es7 syntax at this point in time, and this was an issue I ran across while playing with it. Bergi, I'll definitely fall back to that if there are no other options at this time (which I suspect may be the case). – dvlsg Jun 04 '15 at 17:25
  • @dvlsg note that with Babel you have a bluebirdCoroutines transform that lets you use bluebird promises with native async/await. – Benjamin Gruenbaum Jul 10 '15 at 15:55

2 Answers2

28

Dealing with unhandled rejected native promises (and async/await uses native promises) is a feature supported now in V8. It's used in the latest Chrome to output debugging information when a rejected promise is unhandled; try the following at the Babel REPL:

async function executor() {
  console.log("execute");
}

async function doStuff() {
  console.log("do stuff");
  throw new Error("omg");
}

function handleException() {
  console.error("Exception handled");
}

(async function() {
  try {
      await executor();
      doStuff();
  } catch(e) {
      handleException();
  }
})()

You see that, even though the exception from doStuff() is lost (because we're not using await when we call it), Chrome logs that a rejected promise was unhandled to the console:

Screenshot

This is also available in Node.js 4.0+, though it requires listening to a special unhandledRejection event:

process.on('unhandledRejection', function(reason, p) {
    console.log("Unhandled Rejection at: Promise ", p, " reason: ", reason);
    // application specific logging, throwing an error, or other logic here
});
Glorfindel
  • 19,729
  • 13
  • 67
  • 91
Michelle Tilley
  • 149,782
  • 38
  • 355
  • 303
  • Ah, perfect. Thanks for the links to the the github issues, I will keep an eye on those, and I'll swap to using io.js for the time being when testing what I'm working on. – dvlsg Jun 04 '15 at 17:47
  • 1
    Ah, one last caveat -- I had to add `process.on('unhandledRejection', err => { throw err; });` to get this to work as expected in io.js for now, based off of their [unit tests](https://github.com/nodejs/io.js/blob/master/test/parallel/test-promises-unhandled-rejections.js#L86). Still a little hacky, but I am getting an error thrown with a (sort of) useful stack trace, this way. – dvlsg Jun 04 '15 at 18:06
  • Playing with this, it seems that an error thrown from an `async` function called without `await` cannot be caught. Is this the correct way of looking at it? – Jehan Jun 21 '15 at 06:10
  • @Jehan That is correct. If you don't `await` it, the promise is created but will not throw. The new unhandled promise rejection mechanism in Chrome will catch it, however. – Michelle Tilley Jun 21 '15 at 06:13
  • 1
    @dvlsg doing `process.on('unhandledRejection', err => { throw err; });` is definitely perfectly fine. Also note that since NodeJS and io.js are merging this feature can safely be used as it will land in Node on convergence. – Benjamin Gruenbaum Jul 10 '15 at 15:53
  • 2
    Just as a follow up, now that Node 4.0 is out, I can confirm that `process.on('unhandledRejection', err => { /* ... stuff */ });` works as expected after the iojs merge, and is still necessary to prevent accidentally swallowing uncaught rejections (as opposed to the behavior in Chrome). – dvlsg Sep 14 '15 at 20:40
  • @MichelleTilley In your example, the console doesn't show the message "Exception handled", which leads me to believe that the error doesn't get handled by your try-catch. – trusktr Dec 21 '15 at 02:09
  • @trusktr That's the point; `try`/`catch` around async functions only works when you `await` the function call. Said another way, `await`ing a rejected promises turns it into an exception, but *only* if you `await` it. The point the example is that Chrome has a mechanism built-in for when you accidentally screw this up. :) – Michelle Tilley Dec 21 '15 at 02:11
  • Does this mean unhandled exceptions mapped to the promise rejection as well? So if I were mixing promises and async/await I could do doStuff().catch(error => handleException()) ? – eagspoo May 17 '16 at 17:11
-10

If you are familiar with promises, use them. If not, you can try this example to make you code more asynchronous :)

function fn1(callback) {
    console.log('executing fn1');
    callback({status: true});
}

function fn2(callback) {
    console.log('executing fn2');
    callback({status: false});
}

function test() {
    fn1(function(result) {
        console.log('fn1 executed with status ' + result.status);
    });

    fn2(function(result) {
        console.log('fn2 executed with status ' + result.status);
        if (result.status == false) {
            console.log('error in fn2');
        }
    });
}

test();
  • 1
    Thanks for the response. I am intentionally using ES7 async/await in this scenario, though. I know, I'm being stubborn, and I'm using features which aren't even fully accepted yet, but they improve the syntax and readability of my actual code significantly (it involves long/infinite while loops inside `async` functions with `await` inside the loops). – dvlsg Jun 04 '15 at 17:31