3

I find lots of posts that almost answer the question, but nothing quite works for me.

I have an async function:

const doStuff = async (data)=>{
    if(data == "a bug")
        throw(new Error('Error! Bug found'));
    return "not a bug";
}

I call the function on a route handler:

app.get('/doStuffRoute', async (req, res, next)=>{
    const result = await doStuff("a bug")
                        .catch((err)=>{return next(err);});

    console.log("This shouldn't execute!");
}

The error gets handled just fine by my custom error middleware, prints out the error, etc. But then I still see This shouldn't execute! being printed as well!

From all my research and reading, await X.catch(error) should be identical to try{ await X } catch {error}. And I really prefer .catch(); if at all possible. There are dozens and dozens of tutorials showing this, and even a few stackoverflow posts explicitly stating this to be the case. However I also occasionally find a post cryptically saying "don't use await with .catch()" with no other explanation or detail, so I don't know what to think.

The only hint I can find is adding return before your next(err) call should be enough to halt execution, but it doesn't seem to actually work if return next(err); is inside a .catch() block.

What is going on? Are my sources lying? Am I misunderstanding some basic fundamental concept? I appreciate any help getting me on track.

As a bonus question, why do so many people suggest a wrapper function a-la express-async-handler? Why would this have any different behavior than just calling the async function directly and handling it?

Trevor Buckner
  • 588
  • 2
  • 13
  • The way you're using `.catch()` is converting the exception object into a returned value. You'd need to detect it by checking `result`. – Ouroborus Mar 26 '21 at 21:04
  • That `return next(err);` is returning from the function that was passed to `catch`, not the main `async` function, so nothing is preventing that `console.log` from happening. – user2740650 Mar 26 '21 at 21:05
  • @Ouroborus Could you give an example of what you mean? I'm not sure I understand your comment. – Trevor Buckner Apr 16 '21 at 07:27
  • @user2740650 So `try {await something()} catch{}` is not equivalent to `await something().catch()`? Because `.catch()` is a new function with its own scope separate from the outer async function? Is there, then, a way to exit the "outer" function from inside a `.catch()` (or a `.then()` for that matter?) – Trevor Buckner Apr 16 '21 at 07:37

2 Answers2

1

A rejected promise does not stop further Javascript execution so that's why the .catch() handler doesn't stop anything. In fact, since async functions are asynchronous, your console.log() will even execute before the .catch() handler does. As such, you need to design code flow that respects the promise state.

I would suggest this:

app.get('/doStuffRoute', async (req, res, next) => {
    try {
        const result = await doStuff("a bug");
        console.log("doStuff() resolved");
    } catch(err) {
        console.log("doStuff() rejected");
        next(err);
    }
}

This way, you've clearly delineated the two code paths for a resolved promise and for a rejected promise and you can place code in the appropriate code path.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Would something like the express-async-handler package solve this for me then? Or will I still need to use a try-catch block in the end? – Trevor Buckner Mar 26 '21 at 22:13
  • @TrevorBuckner - Well, most request handlers I write benefit from a custom error handler, not a generic error handler so I don't use something like express-async-handler myself that applies the same error handler to every situation. In most of my request handlers, I attempt to handle all possible errors locally and can then provide an error response that fits the particular request. This is even more important if you have resources that need to be cleaned up properly upon error conditions such as open files or open database connections, – jfriend00 Mar 27 '21 at 00:58
  • I've finally come back to this issue, and have more questions. When you said "since async functions are asynchronous, your console.log() will even execute before the .catch() handler does", that doesn't seem to follow what I'm reading online. Isn't the entire point of the `await` keyword that the rest of the execution is suspended until the awaited function completes? See https://stackoverflow.com/questions/46712077/does-await-guarantee-execution-order-without-assignment-in-javascript – Trevor Buckner Apr 16 '21 at 06:54
  • @TrevorBuckner - `await` suspends only the execution of the function the `await` is within. At the point that function hits the `await`, it suspends the execution of the function and immediately returns a promise from that function. The caller of the function gets that promise and continues to execute. So, `await` only suspends the immediate function, not callers. – jfriend00 Apr 17 '21 at 02:37
  • Correct, and since the `console.log()` is inside the same function that `await` is within, `console.log()` should not execute until the `await` function resolves or rejects. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function – Trevor Buckner Apr 17 '21 at 06:11
  • @TrevorBuckner - So I guess I'm confused about what your question is then? – jfriend00 Apr 18 '21 at 01:14
1

When you do

const result = await doStuff("a bug").catch((err)=>{return next(err);});
console.log(result) // you will see undefined 

because it is trying to evaluate next method when you pass parameter err and store it in result variable, similarly if you do something like this,

  const add = (err)=>{
    if(err instanceof Error){ return 2}
    return 3
   }
const result = await doStuff("a bug").catch((err)=>{return add(err);});
console.log(result) // you will see 2

Answering your question, when you have a return statement inside a callback function only the callback function's execution ends. which means the main parent function will continue it's execution.

const result = await doStuff("a bug").catch((err)=>{return err;});
console.log(result) // now result is an Error object
if(result instanceof Error){
 next(result) //if an error handler is defined call that or call default express error handler
 return //stop execution of route callback
} 
console.log("will NOT execute if there is an error")

but I would like to add there are better way of handling errors for async express routes, This article explains it in detail.

https://zellwk.com/blog/async-await-express/

Dharman
  • 30,962
  • 25
  • 85
  • 135
Nisha Dave
  • 669
  • 1
  • 9
  • 25