Express.js 和 Bluebird - 处理承诺链
Posted
技术标签:
【中文标题】Express.js 和 Bluebird - 处理承诺链【英文标题】:Express.js and Bluebird - Handling the promise chain 【发布时间】:2017-04-02 10:03:35 【问题描述】:在后端 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 的 promise,但是我有两个问题:
看起来很混乱,有没有更好的方法来链接调用和处理响应?
每当我在回复后调用next()
停止请求时,都会继续执行到另一个.then()
。尽管客户端收到了正确的响应,但在服务器日志中我发现执行仍在继续。例如,如果数据库中没有给定用户的帐户,客户端会收到403
响应,但在服务器日志中我看到异常failed to read property displayName of null
,因为没有用户,它应该在@987654326 中停止@ 在res.status(403).send();
之后。
【问题讨论】:
我目前使用的是最新的node版本7,并且开启了--harmony-async-await
,然后你可以使用async/await模式,真的可以整理代码。
你必须return next()
【参考方案1】:
最好使用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
语法(可能使用转译器 - 或使用 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);
);
【讨论】:
太棒了。 Async/await 似乎与承诺的库配合得很好,就是这种情况。 是的,如果router.post
具有promise-aware 功能,我们根本不需要next
回调,那就更棒了:-)【参考方案2】:
要回答你的第二点,你必须在调用next()
后拒绝你的承诺(或者至少返回一些东西,否则后面的行将被执行)。类似的东西
next();
return Promise.reject()
并更改您的捕获,以便在您没有错误时可以正常工作
.catch(err =>
if (err)
next(err)
);
【讨论】:
也许我误解了,但是如果他调用next
,OP 不想再执行then
。因此,一种方法是拒绝承诺(有或没有错误)
感谢您的编辑。没有yourErrorHere
,只有在发生真正的错误时才调用next(err)
,这是我在第一次修订中遗漏的两件重要事情。【参考方案3】:
首先回答你的第二个问题:没有办法打破/停止承诺链,除非你的回调抛出错误
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)
)
【讨论】:
这让事情变得更加复杂。不过,还是感谢您的详细解答!.then(…, data => return data; )
肯定不会做你想做的事
避免使用Promise
constructor antipattern!您的代码中绝对不需要任何 new Promise
调用。
谢谢@Bergi,如果不使用new Promise()
,如何实现reject部分?你可以做一些同步检查,看看 promise 是否应该被拒绝或解决。
请查看this、@João Pereira,你想要的答案是什么?以上是关于Express.js 和 Bluebird - 处理承诺链的主要内容,如果未能解决你的问题,请参考以下文章
nodejs使用request和bluebird编写的http请求模块
使用 bluebird 和 coffeescript 的简单 promise 示例在一半时间内有效
如何使用 Bluebird 为游标和/或集合的 toArray() Promisify Node 的 mongodb 模块?