22

I have an async function that I expect to throw exception on failure. However something seems to preventing this:

by omitting the try catch blocks I expect an exception to be thrown which I want to handle outside of the function.

The actual result I get is somewhat confusing:

(node:10636) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): E11000 duplicate key error index.

(node:10636) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

async f(obj) {
    await db.collection('...').save(obj);
}

I get the same result when I try to catch the exception and throw something else instead:

async f(obj) {
    try {
        await db.collection('...').save(obj);
    } catch(e) {
        throw e.message;
    }
}

The function is called from a try block, so don't see how this is an Unhandled Promise.

I am trying to use f as a parameter of another function:

g(obj, handler) {
    try {
        handler(obj);
    } catch(e);
        ...
    }
}

g(objToSave, f);
user1063963
  • 1,355
  • 6
  • 22
  • 34
  • As a side note, your rethrow is incorrect... You should be using `catch(e) { throw e; }`. Because (1) `e.message` is a string and you should not just throw a string and (2) `e` may include other parameters that would be useful at the next level. – Alexis Wilke Dec 21 '18 at 16:46

3 Answers3

20
async f(obj) {
    try {
        await db.collection('...').save(obj);
    } catch(e) {
        throw e.message;
    }
}

The function is called from a try block, so don't see how this is an Unhandled Promise.

What is unhandled here is the rejection of a promise returned by the f() function, not by the .save() method. So this will not cause that problem:

async f(obj) {
    try {
        await db.collection('...').save(obj);
    } catch(e) {
        console.error(e.message);
    }
}

Throwing an exception in async function always rejects the promise that is returned by that function.

To catch the exception you either have to do this in another async function:

try {
    asyncFunc();
} catch (err) {
    // you have the error here
}

or you can add a rejection handler explicitly:

asyncFunc().catch(err => {
    // you have the error here
});

If you are catching the exception and throwing another exception then you get the same problem, just in a different function.

You either have to add a promise rejection handler and not throw an exception or return a rejected promise there - or run that function in another async function that handles the exception instead of rethrowing the same or new exception.

To sum it up: Every async function returns a promise. Every promise needs to have a rejection handler.

The rejection handler is added using a two-function .then() or with .catch(), or with try { await asyncFunction(); } catch (err) { ... }

When you have a promise rejection with no rejection handler, you will get a warning in older versions of Node and a fatal error in newer versions of Node - see this answer for more details:

Lihai
  • 323
  • 2
  • 6
  • 18
rsp
  • 107,747
  • 29
  • 201
  • 177
  • 2
    I inferred from _"The function is called from a try block"_ that the OP means the `f` function is being called from a `try/catch` block. – robertklep Mar 21 '17 at 12:08
  • The call of the `f` function looks like: `try{ f() } catch(e) { console.log(e) }` inside a non-async function. If I rewrite to `f().catch(...)` it works, however I would prefer another option because `f` is a parameter of the calling function and might not return a promise. – user1063963 Mar 21 '17 at 12:28
  • @user1063963 if you're calling it from a non-async function, it's not going to work: asynchonicity is "viral", each calling function has to either be `async` as well (and you'd use `await`), or you need to properly handle the returned promise (if there is one). You should probably update your question to reflect what _exactly_ it is you're doing. – robertklep Mar 21 '17 at 12:32
  • 1
    I think you forgot an `await` in `try { asyncFunc(); }` – Bergi Mar 21 '17 at 13:23
13

Ultimately, what you're trying to do is calling an asynchronous function f in a synchronous function g, which won't work (that would be the equivalent of being able to turn an asynchronous function into a synchronous function).

Instead, g either has to be async as well, or it should properly handle the promise returned by f. However, in this comment you state that the function represented by f may not always return a promise, in which case the former option would be the easiest to implement:

async g(obj, handler) {
  return await handler(obj);
}

This will also work if handler doesn't return a promise, but just a value (which is documented here).

Calling g (again) requires either an async function, or code to handle its returned promise:

g(objToSave, f).then(...).catch(...)
Community
  • 1
  • 1
robertklep
  • 198,204
  • 35
  • 394
  • 381
1

which I want to handle outside of the function

That's the only thing you forgot to do. (Which is why the warning is complaining about an unhandled rejection). Your function f is working fine.

You cannot throw a synchronous exception from an async function, everything is asynchronous and exceptions will lead to a rejection of the result promise. That is what you will need to catch:

function g(obj, handler) {
    try {
        Promise.resolve(handler(obj)).catch(e => {
            console.error("asynchronous rejection", e);
        });
    } catch(e) {
        console.error("synchronous exception", e);
    }
}
// or just
async function g(obj, handler) {
    try {
        await handler(obj);
//      ^^^^^
    } catch(e) {
        console.error("error", e);
    }
}

g(objToSave, f);
Bergi
  • 630,263
  • 148
  • 957
  • 1,375