Используйте обещание для обработки возвращаемого значения MySQL в node.js

У меня есть опыт работы с Python, и в настоящее время я перехожу на node.js. У меня проблемы с адаптацией к node.js из-за его асинхронного характера.

Например, я пытаюсь вернуть значение из функции MySQL.

function getLastRecord(name)
{
    var connection = getMySQL_connection();

    var query_str =
    "SELECT name, " +
    "FROM records " +   
    "WHERE (name = ?) " +
    "LIMIT 1 ";

    var query_var = [name];

    var query = connection.query(query_str, query_var, function (err, rows, fields) {
        //if (err) throw err;
        if (err) {
            //throw err;
            console.log(err);
            logger.info(err);
        }
        else {
            //console.log(rows);
            return rows;
        }
    }); //var query = connection.query(query_str, function (err, rows, fields) {
}

var rows = getLastRecord('name_record');

console.log(rows);

После некоторого чтения я понимаю, что приведенный выше код не может работать, и мне нужно вернуть обещание из-за асинхронного характера node.js. Я не могу писать код node.js, например python. Как мне преобразовать getLastRecord(), чтобы вернуть обещание, и как мне обработать возвращаемое значение?

На самом деле, я хочу сделать что-то вроде этого;

if (getLastRecord() > 20)
{
    console.log("action");
}

Как это можно сделать в node.js в удобочитаемом виде?

Я хотел бы посмотреть, как в этом случае можно реализовать промисы с помощью bluebird.


person user781486    schedule 11.04.2016    source источник


Ответы (8)


Это будет немного рассеяно, простите меня.

Во-первых, предполагая, что этот код правильно использует API-интерфейс драйвера mysql, вот один из способов, которым вы можете обернуть его для работы с собственным обещанием:

function getLastRecord(name)
{
    return new Promise(function(resolve, reject) {
        // The Promise constructor should catch any errors thrown on
        // this tick. Alternately, try/catch and reject(err) on catch.
        var connection = getMySQL_connection();

        var query_str =
        "SELECT name, " +
        "FROM records " +   
        "WHERE (name = ?) " +
        "LIMIT 1 ";

        var query_var = [name];

        connection.query(query_str, query_var, function (err, rows, fields) {
            // Call reject on error states,
            // call resolve with results
            if (err) {
                return reject(err);
            }
            resolve(rows);
        });
    });
}

getLastRecord('name_record').then(function(rows) {
    // now you have your rows, you can see if there are <20 of them
}).catch((err) => setImmediate(() => { throw err; })); // Throw async to escape the promise chain

Итак, одно: у вас все еще есть обратные вызовы. Обратные вызовы — это просто функции, которые вы передаете чему-то для вызова в какой-то момент в будущем с аргументами по своему выбору. Таким образом, аргументы функции в xs.map(fn), функции (err, result), видимые в узле, а также результат промиса и обработчики ошибок — все это обратные вызовы. Это несколько сбивает с толку людей, которые называют обратные вызовы определенного типа «обратными вызовами», те, которые из (err, result) используются в ядре узла в так называемом «стиле передачи продолжения», иногда называемом «узловыми обратными вызовами» людьми, которым они не очень нравятся. .

По крайней мере, на данный момент (асинхронность/ожидание в конечном итоге появится), вы в значительной степени застряли с обратными вызовами, независимо от того, принимаете ли вы промисы или нет.

Кроме того, я отмечу, что обещания не сразу, очевидно, полезны здесь, поскольку у вас все еще есть обратный вызов. Промисы по-настоящему сияют только тогда, когда вы комбинируете их с Promise.all и накапливаете промисы а-ля Array.prototype.reduce. Но иногда они действительно блестят, и их стоит изучить.

person Josh Holbrook    schedule 12.04.2016
comment
О, и если вы используете промисы, подумайте о bluebird! Он имеет ряд приятных помощников, хорошую понятную производительность и т. д. - person Josh Holbrook; 12.04.2016
comment
Если я использую bluebird, могу ли я взять свою функцию getLastRecord() и сделать что-то вроде Promisify(getLastRecord), а getLastRecord() поддерживает обещание? - person user781486; 12.04.2016
comment
Я думаю, что bluebirdjs.com/docs/api/promise.fromcallback.html что ты хочешь - person Josh Holbrook; 12.04.2016
comment
Обещания являются родными для nodejs, нет необходимости использовать bluebird? - person CFrei; 27.04.2016
comment
кстати: этого блока try..catch можно избежать, так как внутри функции new Promise throw автоматически вызывает reject(err), если не catched. (По крайней мере, для синхронной части функции.) - person CFrei; 27.04.2016
comment
Bluebird не требуется, очевидно, но он поставляется с набором методов, которых нет в нативных промисах. Я также слышал утверждения, что это быстрее, чем текущая нативная реализация, но у меня нет цифр, подтверждающих это. - person Josh Holbrook; 08.07.2016
comment
Это было действительно полезно. Работа над этим решением на самом деле помогла мне с другими аспектами моего кода и подтолкнула меня к другому уровню понимания того, как реализовать промисы и понять их на другом уровне абстракции. - person Ken Ingram; 23.05.2020
comment
Можем ли мы сделать это getMySQL_connection().promise(), а затем await connection.query(...)? - person artsnr; 13.11.2020

Я изменил ваш код, чтобы использовать обещания Q (модуль NPM). Я предположил, что ваша функция getLastRecord(), которую вы указали в приведенном выше фрагменте, работает правильно.

Вы можете перейти по следующей ссылке, чтобы получить модуль Q

Нажмите здесь: документация Q

var q = require('q');

function getLastRecord(name)
{

var deferred = q.defer(); // Use Q 
var connection = getMySQL_connection();

var query_str =
"SELECT name, " +
"FROM records " +   
"WHERE (name = ?) " +
"LIMIT 1 ";

var query_var = [name];

var query = connection.query(query_str, query_var, function (err, rows, fields) {
    //if (err) throw err;
    if (err) {
        //throw err;           
        deferred.reject(err);
    }
    else {
        //console.log(rows);           
        deferred.resolve(rows);
    }
}); //var query = connection.query(query_str, function (err, rows, fields) {

return deferred.promise;
}



// Call the method like this
getLastRecord('name_record')
 .then(function(rows){
   // This function get called, when success
   console.log(rows);
  },function(error){
   // This function get called, when error
   console.log(error);

 });
person Piyush Sagar    schedule 25.04.2016
comment
Пожалуйста, помогите: я застрял с ошибкой [ERR_HTTP_HEADERS_SENT]: не могу установить заголовки после их отправки клиенту, и я потратил много времени, пытаясь решить эту проблему, но безуспешно. Я попробовал подход @Piyush Sagar, но все еще получаю сообщение об ошибке. Мой серверный код использует экспресс-оболочку next.js, которая использует их называемый RequestHandler. Этот обработчик запроса, кажется, возвращает ответ до того, как обещание разрешается. С помощью обратного вызова я использую if/else для установки данных ответа. Вот где я получаю ошибку, даже используя предложенный подход обещания. - person user3422517; 22.02.2020

Я новичок в Node.js и обещаниях. Какое-то время я искал что-то, что удовлетворит мои потребности, и это то, что я в итоге использовал после объединения нескольких найденных примеров. Я хотел иметь возможность получать соединение для каждого запроса и освобождать его сразу после завершения запроса (querySql), или получать соединение из пула и использовать его в рамках Promise.using, или освобождать его, когда захочу (getSqlConnection). Используя этот метод, вы можете объединять несколько запросов один за другим, не вкладывая их друг в друга.

db.js

var mysql = require('mysql');
var Promise = require("bluebird");

Promise.promisifyAll(mysql);
Promise.promisifyAll(require("mysql/lib/Connection").prototype);
Promise.promisifyAll(require("mysql/lib/Pool").prototype);

var pool = mysql.createPool({
    host: 'my_aws_host',
    port: '3306',
    user: 'my_user',
    password: 'my_password',
    database: 'db_name'
});

function getSqlConnection() {
    return pool.getConnectionAsync().disposer(function (connection) {
        console.log("Releasing connection back to pool")
        connection.release();
    });
}

function querySql (query, params) {
    return Promise.using(getSqlConnection(), function (connection) {
        console.log("Got connection from pool");
        if (typeof params !== 'undefined'){
            return connection.queryAsync(query, params);
        } else {
            return connection.queryAsync(query);
        }
    });
};

module.exports = {
    getSqlConnection : getSqlConnection,
    querySql : querySql
};

usage_route.js

var express = require('express');
var router = express.Router();

var dateFormat = require('dateformat');
var db = require('../my_modules/db');
var getSqlConnection = db.getSqlConnection;
var querySql = db.querySql;

var Promise = require("bluebird");

function retrieveUser(token) {
  var userQuery = "select id, email from users where token = ?";
  return querySql(userQuery, [token])
     .then(function(rows){
        if (rows.length == 0) {
          return Promise.reject("did not find user");
        }

        var user = rows[0];
        return user;
     });
}

router.post('/', function (req, res, next) {

  Promise.resolve().then(function () {
    return retrieveUser(req.body.token);
  })
    .then(function (user){
      email = user.email;
      res.status(200).json({ "code": 0, "message": "success", "email": email});
    })
    .catch(function (err) {
      console.error("got error: " + err);
      if (err instanceof Error) {
        res.status(400).send("General error");
      } else {
        res.status(200).json({ "code": 1000, "message": err });
      }
    });
});

module.exports = router;
person MikeL    schedule 05.05.2017
comment
Это довольно модульный и многоразовый. - person Milind; 22.12.2017
comment
очень плохая практика использования var, но, кроме того, очень многоразовая - person Johnty; 16.11.2020

Вам не нужно использовать обещания, вы можете использовать функцию обратного вызова, что-то вроде этого:

function getLastRecord(name, next)
{
    var connection = getMySQL_connection();

    var query_str =
    "SELECT name, " +
    "FROM records " +    
    "LIMIT 1 ";

    var query_var = [name];

    var query = connection.query(query_str, query_var, function (err, rows, fields) {
        //if (err) throw err;
        if (err) {
            //throw err;
            console.log(err);
            logger.info(err);
            next(err);
        }
        else {
            //console.log(rows);
            next(null, rows);
        }
    }); //var query = connection.query(query_str, function (err, rows, fields) {
}

getLastRecord('name_record', function(err, data) {
   if(err) {
      // handle the error
   } else {
      // handle your data

   }
});
person Jordi Ruiz    schedule 11.04.2016
comment
Спасибо. Есть ли способ сделать что-то подобное if (getLastRecord() > 20> или хотя бы сделать его читабельным? - person user781486; 11.04.2016
comment
@ user16891328 Вы должны сделать это внутри обратного вызова, getLastRecord('name_record', function(err, data) { if(err) {} else { if(data.length > 20) }}); - person Jordi Ruiz; 11.04.2016
comment
В порядке. Спасибо. Похоже, другого выхода нет. Код менее читабелен, чем python. - person user781486; 11.04.2016
comment
Ну, проблема в его асинхронном характере, вам нужно дождаться обратного вызова. - person Jordi Ruiz; 11.04.2016
comment
Как вы думаете, почему использование обратного вызова в этом случае лучше обещания? Будет ли использование обещания сделать код более читаемым? - person user781486; 11.04.2016
comment
@user16891328 user16891328 вы можете использовать обещания, если считаете, что ваш код будет более читабельным. getLastRecord('name_record').then(function(data) { if(data.length > 20) { // dosomething } }); - person Jordi Ruiz; 11.04.2016

Используя пакет promise-mysql, логика будет состоять в том, чтобы связать обещания, используя then(function(response){your code})

и

catch(function(response){your code}) для перехвата ошибок из блоков "then", предшествующих блоку catch.

Следуя этой логике, вы будете передавать результаты запроса в виде объектов или массивов, используя return в конце блока. Возврат поможет передать результаты запроса в следующий блок. Тогда результат будет найден в аргументе функции (здесь это test1). Используя эту логику, вы можете связать несколько запросов MySql и код, необходимый для обработки результата и выполнения любых действий.

объект Connection создается глобальным, поскольку каждый объект и переменная, созданные в каждом блоке, являются только локальными. Не забывайте, что вы можете связать больше «тогда» блоков.

var config = {
    host     : 'host',
    user     : 'user',
    password : 'pass',
    database : 'database',

  };
  var mysql = require('promise-mysql');
  var connection;
  let thename =""; // which can also be an argument if you embed this code in a function

  mysql.createConnection(config
  ).then(function(conn){
      connection = conn;
      let test = connection.query('select name from records WHERE name=? LIMIT 1',[thename]);
      return test;
  }).then(function(test1){
      console.log("test1"+JSON.stringify(test1)); // result of previous block
      var result = connection.query('select * from users'); // A second query if you want
      connection.end();
 connection = {};
      return result;
  }).catch(function(error){
      if (connection && connection.end) connection.end();
      //logs out the error from the previous block (if there is any issue add a second catch behind this one)
      console.log(error);
  });
person Nicolas Guérinet    schedule 05.04.2018

Чтобы ответить на ваш первоначальный вопрос: как это можно сделать в node.js в удобочитаемом виде?

Существует библиотека под названием co, которая дает вам возможность писать асинхронный код в синхронном рабочем процессе. Просто посмотрите и npm install co.

Проблема, с которой вы очень часто сталкиваетесь при таком подходе, заключается в том, что вы не получаете Promise обратно из всех библиотек, которые вам нравится использовать. Таким образом, вы должны либо обернуть его самостоятельно (см. ответ @Joshua Holbrook), либо искать обертку (например: npm install mysql-promise)

(Кстати: в дорожной карте ES7 должна быть встроенная поддержка этого типа рабочего процесса с ключевыми словами async await, но его еще нет в узле: список функций узла.)

person CFrei    schedule 27.04.2016

Этого можно добиться довольно просто, например с bluebird, как вы и просили:

var Promise = require('bluebird');

function getLastRecord(name)
{
    return new Promise(function(resolve, reject){
        var connection = getMySQL_connection();

        var query_str =
            "SELECT name, " +
            "FROM records " +
            "WHERE (name = ?) " +
            "LIMIT 1 ";

        var query_var = [name];

        var query = connection.query(query_str, query_var, function (err, rows, fields) {
            //if (err) throw err;
            if (err) {
                //throw err;
                console.log(err);
                logger.info(err);
                reject(err);
            }
            else {
                resolve(rows);
                //console.log(rows);
            }
        }); //var query = connection.query(query_str, function (err, rows, fields) {
    });
}


getLastRecord('name_record')
    .then(function(rows){
        if (rows > 20) {
            console.log("action");
        }
    })
    .error(function(e){console.log("Error handler " + e)})
    .catch(function(e){console.log("Catch handler " + e)});
person Andrej Burcev    schedule 27.04.2016

Я все еще немного новичок в узле, поэтому, возможно, я что-то пропустил, дайте мне знать, как это работает. Вместо того, чтобы запускать асинхронный узел, он просто навязывает его вам, поэтому вам нужно заранее подумать и спланировать его.

const mysql = require('mysql');
const db = mysql.createConnection({
          host: 'localhost', 
          user: 'user', password: 'password', 
          database: 'database',
      });
      db.connect((err) => {
          // you should probably add reject instead of throwing error
          // reject(new Error()); 
          if(err){throw err;}
          console.log('Mysql: Connected');
      });
      db.promise = (sql) => {
          return new Promise((resolve, reject) => {
              db.query(sql, (err, result) => {
                if(err){reject(new Error());}
                else{resolve(result);}
              });
          });
      };

Здесь я использую модуль mysql, как обычно, но вместо этого я создал новую функцию для предварительной обработки обещания, добавив ее в db const. (вы видите это как «соединение» во многих примерах узлов.

Теперь давайте вызовем запрос mysql, используя обещание.

      db.promise("SELECT * FROM users WHERE username='john doe' LIMIT 1;")
      .then((result)=>{
          console.log(result);
      }).catch((err)=>{
          console.log(err);
      });

Я нашел это полезным, когда вам нужно выполнить второй запрос на основе первого запроса.

      db.promise("SELECT * FROM users WHERE username='john doe' LIMIT 1;")
      .then((result)=>{
          console.log(result);
          var sql = "SELECT * FROM friends WHERE username='";
              sql = result[0];
              sql = "';"
          return db.promise(sql);
      }).then((result)=>{
          console.log(result);
      }).catch((err)=>{
          console.log(err);
      });

На самом деле вы должны использовать переменные mysql, но это должно по крайней мере дать вам пример использования промисов с модулем mysql.

Кроме того, с приведенным выше вы все еще можете продолжать использовать db.query обычным способом в любое время в рамках этих промисов, они просто работают как обычно.

Надеюсь, это поможет с треугольником смерти.

person Dillon Burnett    schedule 03.07.2019