[英]Express.js and Bluebird - Handling the promise chain
在后端API中,我有一個登錄路由,應該執行以下操作序列:
給定用戶名和密碼,嘗試針對Active Directory對用戶進行身份驗證。 如果身份驗證失敗,則回復狀態為401.如果成功,則繼續。
在數據庫中查找具有給定用戶名的用戶。 如果未找到回復狀態403,否則繼續。
查找用戶文檔是否包含電子郵件,顯示名稱等詳細信息(如果這不是第一次登錄)。 如果是,則回復用戶對象,否則繼續。
從Active Directory獲取用戶詳細信息並更新數據庫中的用戶對象。 回復更新的對象。
碼:
router.post('/login', (req, res, next) => {
// capture credentials
const username = req.body.username;
const password = req.body.password;
let user = null;
// authenticate
ad.authenticate(username, password)
.then((success) => {
if (!success) {
res.status(401).send(); // authentication failed
next();
}
return User.findOne({ username }).exec();
})
.then((found) => {
if (!found) {
res.status(403).send(); // unauthorized, no account in DB
next();
}
user = found;
if (user.displayName) {
res.status(201).json(user); // all good, return user details
next();
}
// fetch user details from the AD
return ad.getUserDetails(username, password);
})
.then((details) => {
// update user object with the response details and save
// ...
return user.save();
})
.then((update) => {
res.status(201).json(update); // all good, return user object
next();
})
.catch(err => next(err));
});
現在我運行了回調,但它確實是嵌套的。 所以我想嘗試一下藍鳥的承諾,但我有兩個問題:
看起來很混亂,有什么更好的方式來鏈接呼叫和處理響應?
每當我在回復后調用next()
來停止請求時,執行繼續到另一個.then()
。 雖然客戶端收到正確的響應,但在服務器日志中我發現執行仍在繼續。 例如,如果DB中沒有給定用戶的帳戶,則客戶端會收到403
響應,但是在服務器日志中我看到異常failed to read property displayName of null
,因為沒有用戶,它應該已停止在next()
之后res.status(403).send();
。
最好使用if
/ else
來明確哪些分支將執行,哪些不會:
ad.authenticate(username, password).then((success) => {
if (!success) {
res.status(401).send(); // authentication failed
} else {
return User.findOne({ username }).exec().then(user => {
if (!user) {
res.status(403).send(); // unauthorized, no account in DB
} else if (user.displayName) {
res.status(201).json(user); // all good, return user details
} else {
// fetch user details from the AD
return ad.getUserDetails(username, password).then(details => {
// update user object with the response details and save
// ...
return user.save();
}).then(update => {
res.status(201).json(update); // all good, return user object
});
}
});
}
}).then(() => next(), err => next(err));
對於條件評估來說,嵌套then
調用是非常必要的,你不能將它們線性地鏈接起來並在中間“突破”(除了拋出異常,這真的很難看)。
如果你不喜歡所有這些then
回調,您可以使用async
/ await
語法(可能帶有transpiler -或使用藍鳥的Promise.coroutine
與生成語法效仿)。 然后你的整個代碼變成了
router.post('/login', async (req, res, next) => {
try {
// authenticate
const success = await ad.authenticate(req.body.username, req.body.password);
if (!success) {
res.status(401).send(); // authentication failed
} else {
const user = await User.findOne({ username }).exec();
if (!user) {
res.status(403).send(); // unauthorized, no account in DB
} else if (user.displayName) {
res.status(201).json(user); // all good, return user details
} else {
// fetch user details from the AD
const details = await ad.getUserDetails(username, password);
// update user object with the response details and save
// ...
const update = await user.save();
res.status(201).json(update); // all good, return user object
}
}
next(); // let's hope this doesn't throw
} catch(err) {
next(err);
}
});
要回答你的第二點,你必須在調用next()
之后拒絕你的承諾(或者至少返回一些東西,否則后面的行將被執行)。 就像是
next();
return Promise.reject()
並改變你的捕獲,以便它沒有錯誤
.catch(err => {
if (err)
next(err)
});
首先回答你的第二個問題:沒有辦法打破/停止一個承諾鏈,除非你的回調錯誤
doAsync()
.then(()=>{
throw 'sth wrong'
})
.then(()=>{
// code here never runs
})
你可以簡單地嘗試下面的演示來驗證第二個回調是否仍在運行。
doAsync()
.then(()=>{
res.end('end')
})
.then(()=>{
// code here always runs
})
doAsync()
.then(()=>{
return;
})
.then(()=>{
// code here always runs
})
對於你的第一個問題:在then()中使用第二個參數,這意味着拒絕。 每次將邏輯分成兩部分。
var p = new Promise(function(resolve, reject) {
return
ad.auth(username, password).then(()={
// check if 401 needed. If needed, return reject
if (dont needed 401 in your logic)
resolve(username)
else
reject({ msg: 'authentication has failed', status: 401 })
})
});
p
.then( (username)=>{
// this only runs when the previous resolves
return User.findOne({ username }).exec()
}, (data)=>{
// in fact in your case you dont even have to have the reject callback
return data
} )
.then( (found)=>{
return
new Promise(function(resolve, reject) {
if (found && /*your logic to determine it's not 403*/)
resolve(user)
else
reject({ msg: 'unauthorized, no account in DB', status: 403 })
})
} )
.then( (found)=>{
return
new Promise(function(resolve, reject) {
if (found && /*your logic to determine it's not 403*/)
resolve(user)
else
reject({ msg: 'unauthorized, no account in DB', status: 403 })
})
} )
.then( (user)=>{
return
new Promise(function(resolve, reject) {
if (/*your logic to determine it has the full info*/)
resolve(user)
else
return ad.getUserDetails(username, password)
})
} )
.then( (user)=>{
// all is good, do the good logic
}, (data)=>{
// something wrong, so here you can handle all the reject in one place
res.send(data)
} )
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.