Nodejs表达多个回调错误

时间:2017-10-06 21:42:42

标签: node.js mongodb express callback

我正在使用Nodejs Express开发基于两个级别的自制RBAC系统:

  • 首先,验证用户是否具有执行此操作的正确角色。
  • 其次,验证用户是否有正确的计划来执行此操作。

,我创建了一个像这样的中间件:

exports.can = function (resource, action) {
    return function (request, response, next) {
        action = action || request.method;
        if (!request.user) {
            return next(new errors.UnauthorizedError());
        }
        request.user.can(request.user.role, request.user.currentPack, resource, action, function (can, error) {
            if (error) return next(error);
            if (!can) {
                return next(new errors.UnauthorizedError());
            }
            return can;
        });
        return next();
    };
};

我在我的用户模型中添加了这种方法:

const rbac = new RBAC(rbacJson);
const pack = new PACK(packJson);
schema.method('can', function (role, userPack, resource, action, next) {
    let can = false;
    action = action.toUpperCase();
    can = rbac.can(role, resource, action);
    if (can) {
        can = pack.can(userPack, resource, action, function (can) {
            return next(can);
        });
    }
    return next(can);
});

在我的方法pack.can(...)中,我需要像这样执行一个mongoose查询:

PACK.prototype.can = function (pack, resource, action, next) {
    let can = true;
    // some sequantial code
    Trader.count({/* some conditions */}, function (err, count) {
         if(count == 0) return next(true);
         return next(false);
    });
    return can;
};

我的问题是当Mongoose查询的返回是下一个(false)时,我有这个错误:

 Error: Can't set headers after they are sent.
 at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:356:11)
 at ServerResponse.header (/home/invoice/node_modules/express/lib/response.js:730:10)
 at ServerResponse.send (/home/invoice/node_modules/express/lib/response.js:170:12)
 at ServerResponse.json (/home/invoice/node_modules/express/lib/response.js:256:15)
 at ServerResponse.response.apiResponse (/home/invoice/server/config/middlewares/api.js:10:14)
 at /home/invoice/server/controllers/api/invoice/traders.js:130:21
 at /home/invoice/node_modules/mongoose/lib/model.js:3835:16
 at /home/invoice/node_modules/mongoose/lib/services/model/applyHooks.js:162:20
 at _combinedTickCallback (internal/process/next_tick.js:73:7)
 at process._tickDomainCallback (internal/process/next_tick.js:128:9)

经过调查,我发现错误可能是由于双回调呼叫:

  • can = pack.can(userPack, resource, action, function (can) { return next(can); });
  • return next(new errors.UnauthorizedError());

但我不知道如何解决这个问题。 我希望我能很好地解释我的问题。

1 个答案:

答案 0 :(得分:2)

所以让我们从错误开始:

  

发送后无法设置标头。

十分之九,这是因为尝试向同一请求发送两个响应。虽然技术上是正确的,但对标题的引用有点误导。第二个响应将尝试做的第一件事是设置一些标题,这将失败,因为第一个响应已经将它们发送回客户端。

堆栈跟踪可以清楚地指示第二个响应的来源,包括文件名和行号。更难的是追踪第一个响应,通常我只是投入一些额外的控制台记录来解决它。

在你提到的问题中,你相信你已经找到了问题的根源,但在我看来它可能只是冰山一角。您反复使用相同的模式,即使您将其固定在一个可能不够的地方。

在开始讨论之前,让我们从这开始:

return next();

出于本示例的目的,您是否传递错误并不重要,例如: return next(err);,重点是一致的。首先它调用next(),返回undefined。然后它从周围的函数返回undefined。换句话说,它只是一个方便的简写:

next();
return;

我们返回的原因是为了确保在我们调用next()之后没有其他事情发生,我们总是尽力确保调用next()是我们在处理程序中执行的最后一件事,尤其是因为否则我们可能会遇到错误,我们会尝试两次发送响应。

您使用过的(反)模式看起来有点像这样:

obj.doSomething(function() {
    return next(); // next 1
});

return next(); // next 2

同样,无论您是在调用next()还是next(err),这都无关紧要,但这一切都非常重要。需要注意的关键是,下一个1的return只是从传递给doSomething的函数返回。它没有做任何事情来阻止下一个被击中。下一个和下两个都将被调用。

在某些地方,您的代码似乎不清楚它是否尝试同步或异步,同时使用回调和返回值。它确实有点难以确定'正确'代码应该是这样的。具体来说,can的值是应该同步返回还是异步传递给回调?我怀疑它是后者,但目前的代码似乎在两者之间徘徊。确保在您为下一件事情做好准备之前不要致电next(),这一点非常重要,因此,如果您在等待数据库查询,则必须等待#&} 39;打电话给next直到它回来。

就个人而言,我重新命名你的回调,因此它们都不会被称为next,我发现这真的很令人困惑。当我看到next时,我希望它是Express next函数,而不仅仅是一个任意回调。

这有点猜测,但我建议你的中间件应该是这样的:

exports.can = function (resource, action) {
    return function (request, response, next) {
        action = action || request.method;

        if (!request.user) {
            return next(new errors.UnauthorizedError());
        }

        request.user.can(request.user.role, request.user.currentPack, resource, action, function (can, error) {
            if (error) {
                next(error);
            }
            else if (can) {
                next();
            }
            else {
                next(new errors.UnauthorizedError());
            }
        });

        // Do not call next() here
    };
};

用户模型的相关部分将是:

if (can) {
    pack.can(userPack, resource, action, function (can) {
        next(can);
    });
}
else {
    next(can);
}