2017-10-06 58 views
1

我正在一个自制的RBAC系统上快速的NodeJS基于两个层面:快速的NodeJS多个回调错误

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

,我创建这样的中间件:

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(......)我需要执行这样的猫鼬查询:

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; 
}; 

我的问题是当返回猫鼬查询是下一个(假的),我有这样的错误:

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());

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

回答

2

因此,让我们先从错误:

Can't set headers after they are sent.

十次有九次这是试图通过两个响应发送到相同的请求引起的。对标题的引用有点误导,虽然技术上是真的。第二个响应将尝试做的第一件事是设置一些标头,这将失败,因为第一个响应已经将它们发送回客户端。

堆栈跟踪为您清楚地指出了第二个响应的来源,包括文件名和行号。更难的是追踪第一个响应,通常我只是在一些额外的控制台日志记录中找出答案。

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

在我进入的是,让我们开始这样的:

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),这是可行的多少相同。关键要注意的是,下一个的return刚刚从传递给doSomething的函数返回。它没有做任何事情来阻止下一个2被击中。下一个1和下一个2将被调用。

在你的代码似乎不清楚它是试图同步还是异步,同时使用回调和返回值。这使得确定'正确'的代码应该是什么样子有点难。具体而言,can的值应该是同步返回还是异步传递给回调?我怀疑它是后者,但目前的代码似乎在两者之间被撕裂。确保您不要致电next(),直到您准备好接下来的事情发生为止,因此如果您在等待数据库查询,则在返回之前不得拨打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); 
} 
+0

谢谢你很多为你的解释。你所描述的片段'obj.doSomething(function(){next}(); // next 1 }); return next();' 是非常有帮助的。 “在某些地方,你的代码似乎不清楚它是试图同步还是异步”为了回答你的问题,我也有点困惑,因为我还没有真正理解异步的概念。 – medKHELIFI