0

Let's say that I have an API enpoint /register that registers a new user in my application. The function is asynchronous, because I want to use an asynchronous function inside of an AuthController when hashing the password like this:

authRouter.post('/register', async (req: Request, res: Response, next: NextFunction) => {
    const { username, password } = req.body;
    
    const hashedPassword = await AuthController.hashPassword(password, next);

    // do something else

Then, inside my AuthController I test error handling by throwing an error like this:

const hashPassword = async (password: string, next: NextFunction): Promise<void> => {
    try {
        throw new Error('test error');
        //return await bcrypt.hash(password, 10);
    } catch(err) {
        next(err);
    }
} 

However, when I am testing the API the server crashes completely, here's the log:

[1] Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
[1]     at new NodeError (node:internal/errors:400:5)
[1]     at ServerResponse.setHeader (node:_http_outgoing:663:11)
[1]     at ServerResponse.header (C:\Users\renet\Desktop\git\rapidhcm-monorepo\api\node_modules\express\lib\response.js:794:10)
[1]     at ServerResponse.send (C:\Users\renet\Desktop\git\rapidhcm-monorepo\api\node_modules\express\lib\response.js:174:12)
[1]     at ServerResponse.json (C:\Users\renet\Desktop\git\rapidhcm-monorepo\api\node_modules\express\lib\response.js:278:15)
[1]     at ServerResponse.send (C:\Users\renet\Desktop\git\rapidhcm-monorepo\api\node_modules\express\lib\response.js:162:21)
[1]     at C:\Users\renet\Desktop\git\rapidhcm-monorepo\api\dist\src\routes\auth\auth.js:72:25
[1]     at Generator.next (<anonymous>)
[1]     at fulfilled (C:\Users\renet\Desktop\git\rapidhcm-monorepo\api\dist\src\routes\auth\auth.js:28:58)
[1]     at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
[1]   code: 'ERR_HTTP_HEADERS_SENT'
[1] }
[1]
[1] Node.js v18.13.0

I assume what causes the error is the fact that the function does not stop at catch, but instead continues to the '// do something else' part, where at the end it actually tries to send a response. I could probably place the try {} catch(err){} block in the scope of the router function but I don't want that for two reasons:

  1. In my opinion it's bad design - it's easier to read code when each try {} catch{} block is situated in a seperate function
  2. I would have to place one big try {} catch{} block for the whole function, since I can't read the variables from outside of try{} catch{} scopes

EDIT: Here's the '// do something else'' code block for clarity

    try {
        await User.create({ username, password: hashedPassword ?? 's' });
        res.send({ username, password, hashedPassword });
    } catch (error) {
        console.error('Something went wrong when adding a new user', error);
        res.status(400).send({
            message: 'User already exists in the database',
        });
    }
Damian Kowalski
  • 362
  • 2
  • 8

1 Answers1

0

For anyone struggling with this question. I guess I just did not understand the concept of async/await in Javascript. The problem is that by calling await AuthController.hashPassword(password, next) I should be returning a promise. Instead, inside the hashPassword function I am actually instantly throwing an error which is then caught inside my catch(err){} block - and consequently it is calling the next middleware. Unfortunately this does not stop the execution of the 'parent' function as initialy expected. This only catches the error inside the hashPassword function, and the parent function continues normally. I was searching through the web and figured this question is very similar to this:

How do I stop execution in Express.js after catching an error from an Await call?

The only difference I guess is that I am trying to catch the error inside the 'child' function inside try{} catch(err){} blocks

MY SOLUTION: After giving it some thought I think that the best way to structure the code in this situation is to create a wrapper function that will wrap all asynchronous functions and put a .catch(err) handler at the end of the wrapper.

Damian Kowalski
  • 362
  • 2
  • 8