Как превратить вложенный обратный вызов в обещание?

Недавно я начал использовать pg-promise с библиотекой bluebird. Я всегда вкладывал обратный вызов и обрабатывал err в каждом обратном вызове. Я считаю, что оператор catch в обещании выглядит очень аккуратно. Я не уверен, можно ли превратить этот код в базу обещаний?

username = username.toUpperCase();
let text = "SELECT * FROM users WHERE username = $1";
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);
});

person Zanko    schedule 01.02.2017    source источник
comment
у bluebird есть функция обещания, которая будет обещать bcrypt.compare для вас   -  person Jaromanda X    schedule 01.02.2017
comment
@JaromandaX да, я читал об этом, поэтому все, что мне нужно сделать, это добавить ключевое слово Async в конец моей функции?   -  person Zanko    schedule 01.02.2017
comment
да, я думаю, как только это будет обещано - прочитайте документы :p   -  person Jaromanda X    schedule 01.02.2017
comment
I am not sure if it possible to turn this code to promise base любой код можно превратить в промисы, и promisify решение для вашего bcrypt модуля — самое простое.   -  person vitaly-t    schedule 01.02.2017


Ответы (2)


Я предполагаю, что с помощью bluebirds promisify вы бы обещали bcrypt.compare так (вам НЕ ОБЯЗАТЕЛЬНО использовать асинхронную часть имени)

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

потому что userObject в первом .then нужно использовать во втором .then, вы не можете просто связать .then, возвращая compareAsync, потому что тогда следующий .then не будет иметь доступа к userObject

одно исправление - использовать переменную, которая будет в области действия обоих .then (но тьфу)

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;
    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 = $1";
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... которая обещает функции в объекте и добавляет (по умолчанию) постфикс Async к имени функции - я думаю, вы можете выбрать другое имя постфикса, но документация расскажет вам больше

при обещании одной функции вы сами объявляете имя - вышеприведенное могло быть легко

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

тогда вы просто использовали бы trumpIsBigly, где код имеет compareAsync

Последняя возможность

Свернутый вручную обещанный 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 только в том случае, если нет ошибки, И то же верно.

username = username.toUpperCase();
let text = "SELECT * FROM users WHERE username = $1";
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);
});

Что делает «цепочку» очень простой!

person Jaromanda X    schedule 01.02.2017
comment
действительно ответить на большинство моих сомнений! Функция обещания кажется очень сложной. Извините, если это абсурдные вопросы, но что, если в библиотеке уже есть асинхронный постфикс? И насколько стабильно это преобразование на самом деле? Будет ли он ломаться для какой-то функции? - person Zanko; 01.02.2017
comment
The promisify function seems really complicated - правда? передать имя функции... вернуть функцию, которая возвращает обещание... насколько проще она вам нужна? - person Jaromanda X; 01.02.2017
comment
извините: P Действительно новичок в Promise. Еще один простой вопрос: для .catch(function(err)) будет ли err принадлежать первому err, который встречается? В настоящее время я читаю обещание, чтобы понять это больше :) - person Zanko; 01.02.2017
comment
да (и нет)... в этом коде этот улов сработает при первой ошибке, если это в базе данных.один, то ни один из кодов .then не запустится - person Jaromanda X; 01.02.2017
comment
Обещание ничего не говорит о передаче функции, которая уже основана на обещании. Итак, прежде чем я обещаю какую-либо функцию, я должен убедиться, что это нормальная функция обратного вызова? Читая исходный код и т.д. - person Zanko; 01.02.2017
comment
Bluebird .promisify() работает с любой асинхронной функцией, которая использует стиль асинхронных вызовов node.js, где в качестве последнего аргумента используется обратный вызов, и этот обратный вызов принимает два параметра, поэтому он будет вызываться следующим образом callback(err, data). Да, вы должны убедиться, что функция соответствует этим критериям, прежде чем использовать ее с .promisify(). Вам не нужно смотреть на код функции, чтобы определить это, потому что это должна быть та же информация, которая необходима только для самостоятельного использования функции, поэтому она должна быть в документе или комментариях. - person jfriend00; 01.02.2017
comment
@JaromandaX в вашем вложенном примере, должен ли я предоставить еще один улов для этого вложенного обещания? Таким образом я могу поймать ошибку compareAsync? - person Zanko; 02.02.2017
comment
ну нет, если не кинуть ошибку в конце улова, иначе последний потом будет выполняться, а вы этого не хотите - ошибка все равно должна обрабатываться последним уловом - person Jaromanda X; 02.02.2017
comment
Я добавил .catch для compareAsync — обратите внимание, ГДЕ я его поместил и что он выдает, чтобы ни один из следующих .then не вызывался - person Jaromanda X; 02.02.2017
comment
У меня проблемы с визуализацией этого: (Насколько я знаю, есть две цепочки? Одна - основная цепочка, другая - вложенная цепочка обещаний. В основной цепочке есть оператор catch. Как основная цепочка получит ошибку от compareAsync, если они часть другой цепочки? - person Zanko; 02.02.2017
comment
да... думайте о вложенной цепочке как об обещании, которое разрешается или отклоняется... возвращение этого во внешнем then означает, что внешний then примет результат вложенного then, независимо от того, разрешено оно или отклонено... - person Jaromanda X; 02.02.2017
comment
Просто для уточнения. Итак, в исходном примере без catch во вложенной цепочке может ли внешний улов получить ошибку от compareAsync? сейчас я понимаю, что это не может. - person Zanko; 02.02.2017
comment
как я уже сказал, обещание, возвращенное в первом .then, примет: результат внутреннего обещания, если оно будет отклонено (если compareAsync отклонит), то внешнее .then отклонит с причиной отклонения compareAsync, которая будет поймана финал .catch - person Jaromanda X; 02.02.2017
comment
Обещание такое изящное, но такое сложное, когда мне приходится зависеть от предыдущего значения! это не аккуратно хаха - person Zanko; 02.02.2017
comment
есть и другие способы справиться с этим ... подождите, я добавлю еще одно решение, используя обещанную вручную функцию bcrypt.compare: p - person Jaromanda X; 02.02.2017
comment
Давайте продолжим это обсуждение в чате. - person Zanko; 02.02.2017

Это должно расширить ответ @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 — это путь. Но всегда хорошо понимать, что он эффективно делает ;)

person vitaly-t    schedule 01.02.2017
comment
Спасибо, это дало мне больше информации о обещаниях. В одной из ваших документов github.com/vitaly-t/pg-promise /wiki/Common-Mistakes, вы упомянули, что нам следует избегать Promise.all, и прокомментировали: `// не разрешает запросы;` Можете ли вы дать более подробное объяснение, почему он не решается? В конечном счете, запрос не является функцией обещания и должен работать с Promise.all? - person Zanko; 01.02.2017
comment
Дело не в том, что функция обещания не работает, а в том, что Promise.all не гарантирует выполнение обещаний в соответствии с его протоколом, то есть Promise.all гарантирует выполнение всех обещаний только после разрешения самого метода. - person vitaly-t; 01.02.2017
comment
Ясно, Promise.all и Promise объединяют одно и то же? Я собираюсь полностью прочитать Promise, но надеялся получить быстрый ответ :) - person Zanko; 01.02.2017
comment
Promise.all — это способ дождаться разрешения нескольких обещаний... но если хотя бы одно отклонится, Promise.all отклонит - person Jaromanda X; 02.02.2017
comment
@vitaly-t - хорошая работа, я подумывал показать, как обещать и эту функцию, но мой ответ уже был довольно подробным: p - person Jaromanda X; 02.02.2017