Express.js和Bluebird - 处理承诺链

时间:2016-11-18 14:03:12

标签: javascript node.js express promise bluebird

在后端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();

3 个答案:

答案 0 :(得分: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语法(可能使用转发器 - 或使用Bluebird' s {{ 3}}用生成器语法模拟它)。然后你的整个代码变成了

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);
  }
});

答案 1 :(得分:1)

要回答你的第二点,你必须在致电 <p *ngSwitchCase="'completed-survey'">Thanks</p> 后拒绝你的承诺(或者至少返回一些东西,否则后续行将被执行)。像

这样的东西
next()

并更改您的捕获,以便在您没有错误时可以正常工作

next();
return Promise.reject()

答案 2 :(得分: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) 
} )