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:
- In my opinion it's bad design - it's easier to read code when each try {} catch{} block is situated in a seperate function
 - 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',
        });
    }