【问题标题】:Catching errors with ES6 promises and BookshelfJS使用 ES6 Promise 和 BookshelfJS 捕获错误
【发布时间】:2016-07-16 07:08:59
【问题描述】:

我正在向 ExpressJS 应用程序中的 Bookshelf User 模型添加一个非常基本的登录方法,但我无法从用户模型中的登录函数返回的被拒绝承诺中捕获错误。我在 http://bookshelfjs.org/#Model-static-extend 的文档中查看 Bookshelf 的登录示例,但该示例使用 Bluebird,而我正在尝试对内置的 ES6 承诺做同样的事情。

我在 User 模型中的登录方式:

userModel.js

function login(email, password) {
  return new Promise((resolve, reject) => {
    User.where('email', email)
      .fetch({ require: true })
      .then(user => {
        bcrypt.compare(password, user.get('password'), (err, matched) => {
          if (!matched) return reject(new Error('Password didn\'t match!'));
          resolve(user);
        });
      });
  });

实现登录并从书架User模型调用User.login的控制器动作:

usersAuthController.js

function logUserIn(req, res) {
  new User().login(req.body.email, req.body.password)
    .then(user => res.json({ message: 'Login succeeded!' }))
    .catch(User.NotFoundError, () => res.status(404).json({ error: 'User not found!' }) // catch #1
    .catch(err => res.status(401).json({ err: err.message })); // catch #2
}

我的意图是当 Bookshelf 的 User.fetch 方法无法找到具有给定电子邮件的用户时,login() 可以返回拒绝的承诺。在这种情况下,.catch(User.NotFoundError ...) (catch #1) 行应该捕获它并返回 404。我还打算在 bcrypt 确定传递给 login() 的密码不匹配时,login() 返回一个被拒绝的 Promise用户的密码,在这种情况下,User.NotFoundErrorcatch 语句下方的“catch-all”(catch #2)应返回 401。

如果我输入了不正确的密码,上述代码中的logUserIn() 控制器操作将使用错误消息{ error: "Cannot set property 'message' of undefined" } 捕获#2,而不是我在login() 中拒绝的消息'Password didn't match!' 消息。如果我输入不存在的电子邮件,则永远不会发送响应,并且在控制台中会抛出错误 Unhandled rejection CustomError: EmptyResponse。只有有效的输入有效。

尝试修复:直接在模型中捕获User.NotFoundError

我将 catch #1 移到了 User 模型中,因此登录方法现在看起来像:

userModel.js

function login(email, password) {
  return new Promise((resolve, reject) => {
    User.where('email', email)
      .fetch({ require: true })
      .then(user => {
        bcrypt.compare(password, user.get('password'), (err, matched) => {
          if (!matched) return reject(new Error('Password didn\'t match!'));
          resolve(user);
        });
      })
      .catch(User.NotFoundError, () => reject({ error: 'User not found!' }));
  });

这样,我可以正确捕获两个错误(密码错误和不存在的电子邮件),但这样我无法在控制器中指定状态代码。如果找不到具有给定电子邮件的用户,则应返回 404,但如果密码不正确,则应返回 401,但两个错误都会出现在控制器操作(始终返回 401)。

为了解决这个问题,在 User 模型中我可以做 .catch(User.NotFoundError, () => reject({ name: 'NotFoundError', message: 'User not found!' })) 并且在控制器操作中我可以检查我在使用 const statusCode = err.name === 'NotFoundError' ? 404 : 401 时遇到了什么样的错误,但这看起来真的很混乱并且没有抓住重点拥有这些.catch 声明。

有没有办法从模型的登录方法中捕获User.NotFoundError 以及logInUser 中的任何其他错误?为什么我一开始的设置不起作用,在 usersAuthController.js 中同时包含 catch 语句的设置,以及 Cannot set property 'message' of undefined'CustomError: EmptyResponse 错误的含义是什么(它与混合 Bookshelf 的 Bluebird 有关吗?承诺与内置的 ES6 承诺)?处理这个问题的最佳方法是什么?

【问题讨论】:

标签: javascript node.js express es6-promise bookshelf.js


【解决方案1】:

在您的初始实施中,您不会传播任何可能由User.fetch() 引起的拒绝。另外,因为User.fetch() 已经返回了一个promise,用一个新的promise 包装它有点反模式(尽管你仍然需要用一个promise 包装bcrypt.compare(),因为它只适用于回调)。

试试这个:

function login(email, password) {
  return User .where('email', email)
              .fetch({ require: true })
              .then(user => {
                return new Promise((resolve, reject) => {
                  bcrypt.compare(password, user.get('password'), (err, matched) => {
                    if (err)      return reject(err);
                    if (!matched) return reject(new Error('Password didn\'t match!'));
                    resolve(user);
                  });
                })
              });
}

【讨论】:

  • 谢谢!只是为了澄清一下,通常我应该什么时候将东西包装在 Promise 中(如果我明白了,在 .then 中返回 Promise 的目的是为了让我可以链接 .thens)?
  • @satray 如果某些东西已经返回了一个承诺,你就不必包装它。在这种情况下,因为bcrypt.compare 没有返回承诺,它被包装(但“尽可能晚”)。即使您已经处理了.then().catch(),返回承诺(和承诺链)总是一件好事。
猜你喜欢
  • 2015-07-21
  • 1970-01-01
  • 2020-04-17
  • 1970-01-01
  • 2018-06-25
  • 2018-01-14
  • 2016-02-16
  • 2018-07-06
  • 1970-01-01
相关资源
最近更新 更多