使用 promise 处理 MySQL return 中的值 node.js

Use promise to process MySQL return value in node.js

我有 python 背景,目前正在迁移到 node.js。由于其异步性质,我无法适应 node.js。

例如,我正在尝试 return 来自 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 的异步性质,我需要 return 一个承诺。我不能像 python 那样编写 node.js 代码。如何将 getLastRecord() 转换为 return 承诺以及如何处理 returned 值?

其实我想做的事情是这样的;

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

如何以可读的方式在 node.js 中做到这一点?

我想看看在这种情况下如何使用 bluebird 实现承诺。

你不需要使用 promises,你可以使用回调函数,像这样:

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

   }
});

这里有点乱,见谅

首先,假设此代码正确使用 mysql 驱动程序 API,这里有一种方法可以包装它以使用本机承诺:

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) 中的函数参数,node 中看到的 (err, result) 函数以及 promise 结果和错误处理程序都是回调。人们将这种特定类型的回调称为 "callbacks," 节点核心中使用的 (err, result) 的回调有些混淆,在所谓的 "continuation-passing style" 中,有时被人们称为 "nodebacks"不太喜欢他们

至少现在(async/await 最终会出现),无论您是否采用 promises,您几乎都受困于回调。

另外,我会注意到 promises 不是立即的,显然在这里很有用,因为你还有一个回调。只有当您将 Promise 与 Promise.all 结合起来并像 Array.prototype.reduce 一样使用 Promise 累加器时,Promises 才会真正闪耀。但他们 确实 有时会发光,他们 值得学习。

我已修改您的代码以使用 Q(NPM 模块)承诺。 我假设您在上面的代码片段中指定的 'getLastRecord()' 函数正常工作。

您可以参考以下link获取Q模块

Click here : Q documentation

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);

 });

回答您最初的问题:如何以可读的方式在 node.js 中完成?

有一个名为 co 的库,它使您可以在同步工作流中编写异步代码。看看就知道了 npm install co.

您使用该方法经常面临的问题是,您无法从您喜欢使用的所有库中获得 Promise 返回值。所以你要么自己包装它(参见@Joshua Holbrook 的回答)要么寻找一个包装器(例如:npm install mysql-promise

(顺便说一句:它在 ES7 的路线图上使用关键字 async await 原生支持这种类型的工作流,但它还没有在节点中:node feature list。 )

这可以很简单地实现,例如使用蓝鸟,如您所问:

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)});

我是 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;

使用包 promise-mysql 的逻辑是使用 then(function(response){your code})

链接承诺

catch(function(response){your code}) 从 catch 块之前的 "then" 块捕获错误。

按照此逻辑,您将在块末尾使用 return 以对象或数组形式传递查询结果。 return 将有助于将查询结果传递到下一个块。然后,结果将在函数参数中找到(这里是 test1)。使用此逻辑,您可以链接多个 MySql 查询和操作结果所需的代码,并执行任何您想做的事情。

Connection 对象被创建为全局对象,因为在每个块中创建的每个对象和变量都只是本地的。不要忘记您可以链接更多 "then" 个块。

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);
  });

我对节点还是有点陌生​​,所以也许我错过了一些让我知道它是如何工作的。而不是触发异步节点只是将它强加给你,所以你必须提前考虑并计划它。

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 模块,但我创建了一个新函数来提前处理 promise,方法是将其添加到 db const。 (在很多节点示例中,您将其视为 "connection"。

现在让我们使用 promise 调用 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 正常方式,它们就像正常工作一样。

希望这对死亡三角有帮助。

扩展@Dillon Burnett 的回答可能对其他人有帮助

使用 async/await 和参数

db.promise = (sql, params) => {
    return new Promise((resolve, reject) => {
      db.query(sql,params, (err, result) => {
          if(err){reject(new Error());}
             else{resolve(result);}
          });
       });
};
module.exports = db;

   

 async connection(){
    const result = await db.promise("SELECT * FROM users WHERE username=?",[username]);
       return result;
    }