2017-10-06 62 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