承诺链未按预期顺序执行

时间:2016-07-24 17:48:50

标签: javascript promise bluebird

我试图使用一系列承诺来验证用户名和密码,但看到的承诺过早解决。

我在这里涉及两个不同文件的两个方法。

首先,我的控制器,它接收请求并调用我的模型进行身份验证。该模型应该返回一个在结算时将具有身份验证结果的承诺。

这是我的控制器:

router.post('/api/auth-tokens', function (req, res, next) {
    var creds = {user: req.body.user, password: req.body.password};
    people.authenticate(creds)
    .then(function (result) {
      var status = result.valid ? 200: 401; 
      res.sendStatus(status);
    });
});

people是词法范围的模型。以下是上述方法的当前实现:

var bluebird = require('bluebird');
var bcrypt   = require('bcrypt');
var compare  = bluebird.promisify(bcrypt.compare);

exports.authenticate = function (args) {
    var promise = orm.select({
        db: db,
        table: table,
        where: {id: args.user},
        qrm: 'one'
    });

    promise.catch(function (err) {
        var auth_info = { valid: false };
        return auth_info;
    });

    promise.then(function (user) {
        var promise = compare(args.password, user.hash)
        .then(function (same) {
            var auth_info = { valid: same, permissions: user.permissions };
            return auth_info;
        });
        return promise;
    });

    return promise;
};

orm返回一个承诺,如果用户不存在则会抛出错误,或者如果找到用户则解析并生成数据库行。 (这就是我在那里进行promise.catch调用的原因。以防用户不存在)

如果用户确实存在,我想调用bcrypt.compare来比较数据库行中包含的哈希的输入。当异步完成时,我想解析promise链并将控制权返回给控制器,返回results对象。

我遇到的问题是我的控制器中的.then正在立即执行,而orm.select的调用返回了初始承诺的结果。任何人都可以解释为什么会发生这种情况以及如何解决它?

2 个答案:

答案 0 :(得分:2)

这种情况正在发生,因为您已将该回调直接连接到orm.select返回的承诺。相反,您需要使用then返回的承诺。

这是关于承诺的最重要的关键事项之一: then返回新承诺。 (catch也是如此。)如果你的处理函数从thencatch返回一个promise(技术上,任何 thenable ),那么then的新承诺1}}(或catch)返回将根据您返回的承诺解析/拒绝。如果您的处理函数返回非承诺值,则then(或catch)的新承诺将使用该值解析(不被拒绝)。

所以authenticate应该是这样的:

exports.authenticate = function (args) {
    return orm.select({
        db: db,
        table: table,
        where: {id: args.user},
        qrm: 'one'
    })
    .then(function (user) {
        return compare(args.password, user.hash)
        .then(function (same) {
            var auth_info = { valid: same, permissions: user.permissions };
            return auth_info;
        });
    })
    .catch(function (err) {
        var auth_info = { valid: false };
        return auth_info;
    });
};

请注意,更改既适用于您的外部功能,也适用于您在then中执行的操作。另请注意,catch回调中的代码将被调用,无论是来自orm.select的被拒绝的原始承诺,还是来自compare的承诺。

要查看正在发生的事情,请将此代码段与之后的代码段进行比较:

// Returning the original promise
// Usually NOT what you want
function foo() {
  var promise = new Promise(function(resolve) {
    console.log("Starting first promise");
    setTimeout(function() {
      console.log("Resolving first promise");
      resolve("first");
    }, 500);
  });
  promise.then(function() {
    promise = new Promise(function(resolve) {
      console.log("Starting second promise");
      setTimeout(function() {
        console.log("Resolving second promise");
        resolve("second");
      }, 500);
    });
  });
  return promise;
}
foo().then(function(result) {
  console.log("Got result: " + result);
});

第二

// Returning the original promise
// Usually NOT what you want
function foo() {
  return new Promise(function(resolve) {
    console.log("Starting first promise");
    setTimeout(function() {
      console.log("Resolving first promise");
      resolve("first");
    }, 500);
  })
  .then(function() {
    return new Promise(function(resolve) {
      console.log("Starting second promise");
      setTimeout(function() {
        console.log("Resolving second promise");
        resolve("second");
      }, 500);
    });
  });
}
foo().then(function(result) {
  console.log("Got result: " + result);
});

答案 1 :(得分:1)

您的控制器正在执行与使用bcrypt.compare

的承诺不同的承诺

这样做是为了确保people.authenticate返回一个声明链,该声明链解析为比较后的内部结果:

var promise = orm.select(/* ... */)
    .then(/* bcrypt usage here */)
    .catch(/* error handling here */);

return promise;