Skip to content Skip to sidebar Skip to footer

How To Turn Nested Callback Into Promise?

Recently I started using pg-promise with bluebird library. I have always been nesting callback and handling err in each callback. I find that the catch statement in promise looks r

Solution 1:

I imagine using bluebirds promisify, you would promisify bcrypt.compare like so (you don't HAVE to use the Async part of the name)

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

one fix, is to use a variable that will be in scope for both .then's (but ugh)

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

another (in my opinion cleaner) option is a nested .then

username = username.toUpperCase();
let text = "SELECT * FROM users WHERE username = $1";
let values = [username];
database.one(text, values).then(function (userObject) {
    returncompareAsync(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) {
        thrownewError("Password mismatched!");
    }
    let serializeObject = { _id: userObject._id };
    returnnext(null, serializeObject);
}).catch(function (err) {
    returnnext(err, null);
});

NOTE: bluebird has a promisifyAll function ... that promisifies functions in an object, and adds (by default) the Async postfix to the function name - I believe you can decide on a different postfix name, but the documentation will tell you more

when promisifying a single function, you declare the name yourself - the above could've easily been

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

then you would just use trumpIsBigly where the code has compareAsync

One last possibility

A hand rolled promisified compareAsync (lifted mostly from vitaly-t's answer but with additions)

functioncompareAsync(password1, password2, inValue) {
    returnnewPromise(function (resolve, reject) {
        bcrypt.compare(password1, password2, function (err, same) {
            err = err || (!same && newError("Password mismatched!"));
            if (err) {
                reject(err);
            } else {
                resolve(inValue);
            }
        });

    });
}

Now compareAsync will resolve to the incoming value inValue only if there's no error, AND same is true

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

Which makes the "chain" very simple!

Solution 2:

This is to extend on @Jaromanda's answer, in case you use only that one function, and just want to see how to promisify it manually.

functionsamePassword(password1, password2) {
    returnnewPromise(function (resolve, reject) {
        bcrypt.compare(password1, password2, (err, same) => {
            err = err || (!same && newError("Password mismatched!"));
            if (err) {
                reject(err);
            } else {
                resolve();
            }
        });

    });
}

db.one(text, values)
    .then(userObject => {
        returnsamePassword(password, userObject.password);
    })
    .catch(error => {
        returnnext(error, null);
    });

Other than that, the promisify approach is the way to go. But it is always good to understand what it effectively does ;)

Post a Comment for "How To Turn Nested Callback Into Promise?"