2016-11-18 90 views
3

在后端API我有应执行以下动作序列的登录路由:Express.js和蓝鸟 - 处理的承诺链

  • 给定一个用户名和密码,尝试对用户进行身份验证一个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()。虽然客户端收到正确的响应,但在服务器日志中,我发现执行仍在继续。例如,如果给定用户的数据库中没有帐户,则客户端收到403响应,但在服务器日志中,我看到一个例外failed to read property displayName of null,因为没有用户,它应该在res.status(403).send();之后的next()处停止。

+1

我目前使用的是最新的节点版本7,并启用了'--harmony-async-await',然后可以使用async/await模式,真的可以清理代码。 – Keith

+0

你必须'return next()' – ThomasThiebaud

回答

2

最佳使用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); 
    } 
}); 
+0

太棒了。异步/等待似乎与promisifyed库非常相符,情况就是如此。 –

+0

是的,如果'router.post'有承诺意识,那么就更加可怕了,所以我们根本不需要'next'回调:-) – Bergi

1

要回答你的第二个问题,您可以选择拒绝呼叫next()后,你的承诺(或至少一些回报,否则行之后将被执行)。像

next(); 
return Promise.reject() 

,改变你的捕捉,所以如果你没有一个错误

.catch(err => { 
    if (err)  
    next(err) 
}); 
+0

但是调用next()时没有错误! – Bergi

+0

也许我误解了,但是如果OP调用next来,OP不再执行'then'。所以一种方法是拒绝承诺(有或没有错误) – ThomasThiebaud

+1

感谢您的编辑。没有'yourErrorHere',只有在发生*真实*错误时调用'next(err)'是我在第一次修订中错过的两件重要事情。 – Bergi

0

你的第二个问题,首先,它的工作原理的东西:有没有办法破解/停止许诺链,除非你回调抛出错误像

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) 
}) 
+0

这使事情变得更加复杂。尽管如此,谢谢你的详细解答! –

+0

'.then(...,data => {return data;})''确实不*你想做什么 – Bergi

+0

避免['Promise' constructor antipattern](http://stackoverflow.com/q/23803743/1048572 )!你绝对不需要在你的代码中使用任何'新的Promise'调用。 – Bergi