0
votes

I have a route handler like this:

router.route('/callcenter/:callcenter_id/contactgroup/:contactgroup_id')
    .delete((req, res) => {
        if (typeof req.body.creator === 'undefined') {
            return res.status(400).json({
                success: false,
                error: { message: 'Invalid input' }
            });
        }
        const ContactGroup = new ContactGroupModel(db, req.params.callcenter_id, logger);
        ContactGroup.read(req.params.contactgroup_id)
            .then((result) => {
                if (!result) {
                    return res.status(404).json({
                        success: false,
                        error: { message: 'Contact group not found' }
                    });
                }
                if (req.body.creator !== result.creator) {
                    return res.status(400).json({
                        success: false,
                        error: { message: 'Invalid input' }
                    });
                }
                return ContactGroup.delete(req.params.contactgroup_id);
            })
            .then((result) => {
                if (!result) {
                    return res.status(404).json({
                        success: false,
                        error: { message: 'Contact group not found' }
                    });
                }
                return res.json({ success: true });
            })
            .catch((error) => res.status(400).json({
                success: false,
                error: { message: error }
            }));
    });

Both read and delete functions of ContactGroup are promises. And I write several tests:

describe('DELETE', () => {
            let id;
            beforeEach(() => ContactGroup.create(data).then((result) => id = result._id));
            it('Should return 200 if successful', (done) => {
                chai.request(app)
                    .delete('/callcenter/test/contactgroup/' + id)
                    .send({ creator: 'user__1' })
                    .end((err, res) => {
                        expect(res.status).to.equal(200);
                        expect(res.body.success).to.equal(true);
                        return done();
                    });
            });
            it('Should return 400 if input is invalid (without creator)', (done) => {
                chai.request(app)
                    .delete('/callcenter/test/contactgroup/' + id)
                    .end((err, res) => {
                        expect(res.status).to.equal(400);
                        expect(res.body.success).to.equal(false);
                        expect(res.body.error.message).to.equal('Invalid input');
                        return done();
                    });
            });
            it('Should return 400 if input is invalid (unmatched creator)', (done) => {
                chai.request(app)
                    .delete('/callcenter/test/contactgroup/' + id)
                    .send({ creator: 'user__2' })
                    .end((err, res) => {
                        expect(res.status).to.equal(400);
                        expect(res.body.success).to.equal(false);
                        expect(res.body.error.message).to.equal('Invalid input');
                        return done();
                    });
            });
            it('Should return 404 if not found', (done) => {
                ContactGroup.delete(id).then(
                    () => {
                        chai.request(app)
                            .delete('/callcenter/test/contactgroup/' + id)
                            .send({ creator: 'user__1' })
                            .end((err, res) => {
                                expect(res.status).to.equal(404);
                                expect(res.body.success).to.equal(false);
                                expect(res.body.error.message).to.equal('Contact group not found');
                                return done();
                            });
                    });
            });
            afterEach(() => ContactGroup.delete(id));
        });

All of them pass, but the logger records some warnings in the last two tests containg UnhandledPromiseRejectionWarning: Unhandled promise rejection, Can't set headers after they are sent

I haven't got why the ultimate catch block in route handler gets called. I thought only when the promise function gets rejected, catch will occur

1
add return before res.json() in catch blockRajan Chauhan

1 Answers

1
votes

Your code attempts to send multiple responses for the same request and that's why you get the message about "headers have already been sent".

You have code like this:

ContactGroup.read(req.params.contactgroup_id).then(...).then(...).catch(...)

And, there are code paths where you can end up sending a response in both of those .then() handlers which causes that error. In your first .then() handler, it appears that you think that doing:

return res.status(404).json(...)

stops the promise chain. It does not. The promise chain continues and goes right onto the next .then() handler. Since res.status() doens't return anything, it will go to the next .then() handler with undefined as the resolved value. That will cause you to then do:

return res.status(404).json(...)

which causes the message about headers already being sent.


I'm not sure the exact flow you want, but perhaps you want something like this where you nest the second .then() so it doesn't execute when you've previously done a return:

router.route('/callcenter/:callcenter_id/contactgroup/:contactgroup_id').delete((req, res) => {
    if (typeof req.body.creator === 'undefined') {
        return res.status(400).json({
            success: false,
            error: {
                message: 'Invalid input'
            }
        });
    }
    const ContactGroup = new ContactGroupModel(db, req.params.callcenter_id, logger);
    ContactGroup.read(req.params.contactgroup_id).then((result) => {
        if (!result) {
            return res.status(404).json({
                success: false,
                error: {
                    message: 'Contact group not found'
                }
            });
        }
        if (req.body.creator !== result.creator) {
            return res.status(400).json({
                success: false,
                error: {
                    message: 'Invalid input'
                }
            });
        }
        return ContactGroup.delete(req.params.contactgroup_id).then((result) => {
            if (!result) {
                return res.status(404).json({
                    success: false,
                    error: {
                        message: 'Contact group not found'
                    }
                });
            }
            return res.json({
                success: true
            });
        });
    }).catch((error) => res.status(400).json({
        success: false,
        error: {
            message: error
        }
    }));
});