【问题标题】:Warning - Unhandled promise rejection: Can't set headers after they are sent警告 - 未处理的承诺拒绝:发送后无法设置标头
【发布时间】:2018-01-04 00:00:21
【问题描述】:

我有一个这样的路由处理程序:

router.route('/sipprovider/:sipprovider_id/phonelist')
        .post((req, res) => {
            const body = req.body;
            if (typeof body.phones === 'undefined') {
                return res.status(400).json({
                    success: false,
                    error: { message: 'Invalid input' }
                });
            }
            if (!Array.isArray(body.phones) || body.phones.length === 0) {
                return res.status(400).json({
                    success: false,
                    error: { message: 'Invalid input' }
                });
            }
            let i = body.phones.indexOf('');
            while (i >= 0) {
                body.phones.splice(i, 1);
                i = body.phones.indexOf('');
            }
            body.phones.map((phone) => {
                if (typeof phone !== 'string') {
                    return res.status(400).json({
                        success: false,
                        error: { message: 'Invalid input' }
                    });
                }
            });
            const SipProvider = new SipProviderModel(db, logger);
            SipProvider.pushPhoneList(req.params.sipprovider_id, body.phones)
                .then((result) => {
                    if (result) {
                        return res.json({ success: true });
                    }
                    return res.status(404).json({
                        success: false,
                        error: { message: 'Sip provider not found' }
                    });
                })
                .catch((error) => res.status(400).json({
                    success: false,
                    error: { message: error }
                }));
        });

SipProviderpushPhoneList 函数是一个承诺。我写了几个测试:

describe('POST', () => {
            let id;
            beforeEach(() => SipProvider.create(data).then((result) => id = result._id));
            it('Should return 200 if successful', (done) => {
                chai.request(app)
                    .post('/sipprovider/' + id + '/phonelist')
                    .set('Authorization', token)
                    .send({ phones: ['02873000050'] })
                    .end((err, res) => {
                        expect(res.status).to.equal(200);
                        expect(res.body.success).to.equal(true);
                        return done();
                    });
            });
            it('Should return 200 if successful', (done) => {
                chai.request(app)
                    .post('/sipprovider/' + id + '/phonelist')
                    .set('Authorization', token)
                    .send({ phones: ['02873000050', ''] })
                    .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 (undefined phones)', (done) => {
                chai.request(app)
                    .post('/sipprovider/' + id + '/phonelist')
                    .set('Authorization', token)
                    .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 (not an array)', (done) => {
                chai.request(app)
                    .post('/sipprovider/' + id + '/phonelist')
                    .set('Authorization', token)
                    .send({ phones: '02873000050' })
                    .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 (empty list)', (done) => {
                chai.request(app)
                    .post('/sipprovider/' + id + '/phonelist')
                    .set('Authorization', token)
                    .send({ phones: [] })
                    .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 (not an array of string)', (done) => {
                chai.request(app)
                    .post('/sipprovider/' + id + '/phonelist')
                    .set('Authorization', token)
                    .send({ phones: ['02873000050', {}] })
                    .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) => {
                SipProvider.delete(id).then(
                    () => {
                        chai.request(app)
                            .post('/sipprovider/' + id + '/phonelist')
                            .set('Authorization', token)
                            .send({ phones: ['02873000050'] })
                            .end((err, res) => {
                                expect(res.status).to.equal(404);
                                expect(res.body.success).to.equal(false);
                                expect(res.body.error.message).to.equal('Sip provider not found');
                                return done();
                            });
                    });
            });
            afterEach(() => SipProvider.delete(id));
        });

它们都通过了,但是记录器在最后一次测试中记录了一些警告,其中包含UnhandledPromiseRejectionWarning: Unhandled promise rejection, Can't set headers after they are sent

我不明白为什么调用路由处理程序中的最终 catch 块。我以为只有当 promise 函数被拒绝时,才会发生 catch

【问题讨论】:

  • 想想body.phones.map((phone) => { 中的代码——理论上你可以res.status(400).json({ 多次...然后继续SipProvider.pushPhoneList(req.params.sipprovider_id, body.phones) 无论如何

标签: javascript node.js unit-testing header promise


【解决方案1】:

首先,在这段代码中:

        body.phones.map((phone) => {
            if (typeof phone !== 'string') {
                return res.status(400).json({
                    success: false,
                    error: { message: 'Invalid input' }
                });
            }
        });

您最终可以多次致电res.status()。这很容易导致“发送后无法设置标题”的问题。这里的问题是return 只从.map() 回调中返回。它不会停止.map() 中的其他回调,也不会阻止执行该块之后的其余代码,这也会导致发送另一个响应。

您可以通过执行以下操作来解决该问题:

if (!body.phones.every(phone => typeof phone === 'string')) {
     return res.status(400).json({
         success: false,
         error: { message: 'Invalid input' }
     });
}

这将遍历所有body.phones 项目,测试它们是否都是正确的类型,然后在循环之外,如果它们不是所有正确的类型,那么它将return。这只会发送一个响应,并会从外部函数返回,停止进一步执行。

【讨论】:

  • @necroface - 您可能在某处仍有未处理的拒绝,但现在因为您正确检测到无效的电话数据,您不再触发它。我的猜测是未处理的拒绝在 SipProvider.pushPhoneList() 的实现内部,因为我在您提供的代码中没有看到。
猜你喜欢
  • 1970-01-01
  • 2017-12-20
  • 2018-04-26
  • 2017-09-08
  • 1970-01-01
  • 1970-01-01
  • 2021-06-03
  • 2018-11-07
  • 2023-04-04
相关资源
最近更新 更多