Express.js 和 Bluebird - 处理承诺链
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));
});
现在我有了这个 运行 回调,但它确实是嵌套的。所以我想试试 Bluebird promises,但我有两个问题:
看起来很混乱,有更好的方法来链接调用和处理响应吗?
每当我在回复后调用next()
停止请求时,执行会继续到另一个.then()
。尽管客户端收到了正确的响应,但在服务器日志中我发现执行仍在继续。例如,如果给定用户在数据库中没有帐户,客户端会收到 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
调用的嵌套对于条件评估来说是非常必要的,你不能将它们线性链接起来 "break out" 在中间(除了抛出异常,这真的很难看)。
如果您不喜欢所有这些 then
回调,您可以使用 async
/await
语法(可能使用转译器 - 或者使用 Bluebird 的 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()
(或至少 return 之后拒绝你的承诺,否则将执行之后的行)。像
next();
return Promise.reject()
并更改您的捕获,以便在您没有错误的情况下正常工作
.catch(err => {
if (err)
next(err)
});
首先回答你的第二个问题:没有办法 break/stop 承诺链,除非你的回调抛出错误
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()中使用第二个参数,表示reject。并且每次都将逻辑分成两部分。
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)
} )
在后端 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));
});
现在我有了这个 运行 回调,但它确实是嵌套的。所以我想试试 Bluebird promises,但我有两个问题:
看起来很混乱,有更好的方法来链接调用和处理响应吗?
每当我在回复后调用
next()
停止请求时,执行会继续到另一个.then()
。尽管客户端收到了正确的响应,但在服务器日志中我发现执行仍在继续。例如,如果给定用户在数据库中没有帐户,客户端会收到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
调用的嵌套对于条件评估来说是非常必要的,你不能将它们线性链接起来 "break out" 在中间(除了抛出异常,这真的很难看)。
如果您不喜欢所有这些 then
回调,您可以使用 async
/await
语法(可能使用转译器 - 或者使用 Bluebird 的 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()
(或至少 return 之后拒绝你的承诺,否则将执行之后的行)。像
next();
return Promise.reject()
并更改您的捕获,以便在您没有错误的情况下正常工作
.catch(err => {
if (err)
next(err)
});
首先回答你的第二个问题:没有办法 break/stop 承诺链,除非你的回调抛出错误
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()中使用第二个参数,表示reject。并且每次都将逻辑分成两部分。
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)
} )