如何将嵌套回调变成承诺?

How to turn nested callback into promise?

最近我开始将 pg-promise 与 bluebird 库一起使用。我一直在嵌套回调并在每个回调中处理 err 。我发现 promise 中的 catch 语句看起来非常整洁。我不确定是否可以将此代码转换为 promise base?

username = username.toUpperCase();
let text = "SELECT * FROM users WHERE username = ";
let values = [username];
database.one(text, values).then(function (userObject) {
  // ANY WAY TO TURN this nested bycrypt into promise chain?
  bcrypt.compare(password, userObject.password, function (err, same) {
    if (err) {
      return next(err, null);
    }
    if (!same) {
      return next(new Error("Password mismatched!"), null);
    }
    const serializeObject = {_id: userObject._id};
    return next(null, serializeObject);
  });
}).catch(function (err) {
  return next(err, null);
});

我想象使用 bluebirds promisify,你会像这样 promisify bcrypt.compare(你不必使用名称的 Async 部分)

let compareAsync = Promise.promisify(bcrypt.compare);

because userObject in the first .then needs to be used in the second .then, you can't simply chain the .then's by returning compareAsync, because then the next .then wont have access to userObject

一个解决方法是使用一个变量,该变量将在 .then 的范围内(但是呃)

username = username.toUpperCase();
let text = "SELECT * FROM users WHERE username = ";
let values = [username];
let uo; //hacky the outer scoped variable
database.one(text, values).then(function (userObject) {
    uo = userObject;
    return compareAsync(password, userObject.password);
}).then(function(same) {
    if (!same) {
        throw new Error("Password mismatched!");
    }
    const serializeObject = {_id: uo._id};
    return next(null, serializeObject);
}).catch(function (err) {
    return next(err, null);
});

另一个(在我看来更干净)选项是嵌套的 .then

username = username.toUpperCase();
let text = "SELECT * FROM users WHERE username = ";
let values = [username];
database.one(text, values).then(function (userObject) {
    return compareAsync(password, userObject.password)
    // [optional] following three lines to generate a "nicer" error for compare failure
    .catch(function(err) {
        throw "bcrypt.compare failed";
    })
    // nested .then to pass on the userObject and same at the same time
    .then(function (same) {
        return { same: same, userObject: userObject };
    });
}).then(function (result) {
    let same = result.same,
        userObject = result.userObject;

    if (!same) {
        throw new Error("Password mismatched!");
    }
    let serializeObject = { _id: userObject._id };
    return next(null, serializeObject);
}).catch(function (err) {
    return next(err, null);
});

注意:bluebird 有一个 promisifyAll 函数......它在一个对象中 promisify 函数,并添加(默认)Async 后缀到函数名称 - 我相信你可以决定一个不同的后缀名称,但文档会告诉您更多信息

当 promisify 单个函数时,您自己声明名称 - 以上内容很容易做到

let trumpIsBigly = Promise.promisify(bcrypt.compare);

那么你只需使用 trumpIsBigly 代码有 compareAsync

One last possibility

手推 promisified compareAsync(主要来自 vitaly-t 的回答,但有补充)

function compareAsync(password1, password2, inValue) {
    return new Promise(function (resolve, reject) {
        bcrypt.compare(password1, password2, function (err, same) {
            err = err || (!same && new Error("Password mismatched!"));
            if (err) {
                reject(err);
            } else {
                resolve(inValue);
            }
        });

    });
}

现在 compareAsync 将解析为传入值 inValue 只有在没有错误的情况下,AND 同样如此

username = username.toUpperCase();
let text = "SELECT * FROM users WHERE username = ";
let values = [username];
database.one(text, values).then(function (userObject) {
    return compareAsync(password, userObject.password, userObject)
}).then(function (userObject) {
    let serializeObject = { _id: userObject._id };
    return next(null, serializeObject);
}).catch(function (err) {
    return next(err, null);
});

这使得 "chain" 非常简单!

这是对@Jaromanda 的回答的扩展,以防你只使用那个功能,并且只想看看如何手动承诺它。

function samePassword(password1, password2) {
    return new Promise(function (resolve, reject) {
        bcrypt.compare(password1, password2, (err, same) => {
            err = err || (!same && new Error("Password mismatched!"));
            if (err) {
                reject(err);
            } else {
                resolve();
            }
        });

    });
}

db.one(text, values)
    .then(userObject => {
        return samePassword(password, userObject.password);
    })
    .catch(error => {
        return next(error, null);
    });

除此之外,promisify 方法是必经之路。但了解它的有效作用总是好的 ;)