Async.js解决Node.js操作MySQL的回调大坑
2015-12-22 12:39
771 查看
由于JavaScript语言异步特性,在使用Node.js执行很多操作时都会使用到回调函数,其中就包括访问数据库。如果代码中的业务逻辑稍微复杂一点,回调一层层嵌套,那么代码很容易进入Callback Hell,无论对写代码的人还是阅读代码的人,都是精神上的折磨。
例如对MySQL的一个事务操作,插入一条posts并插入一条log:
以上非常简单的一个业务逻辑,已经回调了好几层了,如果稍微再复杂一点,那么代码恐怕就无法直视了。
为了防止发生多层嵌套回调的大坑,可以使用Async.js来解决这个问题。下面来介绍Async.js结合操作MySQL数据库的使用。
async.each并不能保证执行成功一条SQL语句后再去执行下一条,所以如果有一条执行失败,不会影响到其他语句的执行。
async.eachSeries保证了SQL的执行顺序,而且当其中一条执行异常,就不会继续执行下一条。
运行结果:
{ table_a: 26, table_b: 3, table_c: 2 }
运行结果:
{ table_a: 26, table_b: 3, table_c: 2 }
运行结果:
{ table_a: 26, table_b: 3, table_c: 2 }
以上是Async.js操作数据库经常会用到的一些例子,用上它的话就不再需要担心异步回调的坑了。Async.js不仅仅可以用于数据库操作,其他用到异步回调函数的地方都可以使用,例如文件读写等,本文只是通过数据库操作为例来介绍Async.js基本用法,它同样也可以运用到其他需要它的地方。除了上面介绍的几个函数外,Async.js还提供了一些其他实用的函数,可以参考文档灵活使用。
例如对MySQL的一个事务操作,插入一条posts并插入一条log:
[code]var title = 'It is a new post'; connection.beginTransaction(function(err) { if (err) { throw err; } connection.query('INSERT INTO posts SET title=?', title, function(err, result) { if (err) { return connection.rollback(function() { throw err; }); } var log = 'Post ' + result.insertId + ' added'; connection.query('INSERT INTO log SET data=?', log, function(err, result) { if (err) { return connection.rollback(function() { throw err; }); } connection.commit(function(err) { if (err) { return connection.rollback(function() { throw err; }); } console.log('success!'); }); }); }); });
以上非常简单的一个业务逻辑,已经回调了好几层了,如果稍微再复杂一点,那么代码恐怕就无法直视了。
为了防止发生多层嵌套回调的大坑,可以使用Async.js来解决这个问题。下面来介绍Async.js结合操作MySQL数据库的使用。
async.each批量Insert
假设需求是向log表中插入多条数据,最终返回执行结果,可以使用async.each函数:[code]var sqls = [ "INSERT INTO log SET data='data1'", "INSERT INTO log SET data='data2'", "INSERT INTO log SET data='data3'" ]; async.each(sqls, function(item, callback) { // 遍历每条SQL并执行 connection.query(item, function(err, results) { if(err) { // 异常后调用callback并传入err callback(err); } else { console.log(item + "执行成功"); // 执行完成后也要调用callback,不需要参数 callback(); } }); }, function(err) { // 所有SQL执行完成后回调 if(err) { console.log(err); } else { console.log("SQL全部执行成功"); } });
async.each并不能保证执行成功一条SQL语句后再去执行下一条,所以如果有一条执行失败,不会影响到其他语句的执行。
async.eachSeries按顺序批量Insert
如果想要实现执行成功上一条语句后再开始执行数组中下一条语句,可以使用eachSeries函数:[code]var sqls = [ "INSERT INTO log SET data='data1'", "INSERT INTO log SET data='data2'", "INSERT INTO log SET data='data3'" ]; async.eachSeries(sqls, function(item, callback) { // 遍历每条SQL并执行 connection.query(item, function(err, results) { if(err) { // 异常后调用callback并传入err callback(err); } else { console.log(item + "执行成功"); // 执行完成后也要调用callback,不需要参数 callback(); } }); }, function(err) { // 所有SQL执行完成后回调 if(err) { console.log(err); } else { console.log("SQL全部执行成功"); } });
async.eachSeries保证了SQL的执行顺序,而且当其中一条执行异常,就不会继续执行下一条。
async.forEachOf获取多条Select语句的查询结果
async.forEachOf类似于async.each,区别是可以接收Object类型参数,并且会在第二个参数回调函数中传入遍历到的每一项的key,更适合批量执行查询语句并返回结果:[code]var sqls = { table_a: "select count(*) from table_a", table_b: "select count(*) from table_b", table_c: "select count(*) from table_c" }; // 用于存放查询结果 var counts = {}; async.forEachOf(sqls, function(value, key, callback) { // 遍历每条SQL并执行 connection.query(value, function(err, results) { if(err) { callback(err); } else { counts[key] = results[0]['count(*)']; callback(); } }); }, function(err) { // 所有SQL执行完成后回调 if(err) { console.log(err); } else { console.log(counts); } });
运行结果:
{ table_a: 26, table_b: 3, table_c: 2 }
async.map简化获取多条Select语句的查询结果
上面的async.forEachOf获取多条Select语句的查询结果的代码可以使用async.map函数简化成这样:[code]var sqls = { table_a: "select count(*) from table_a", table_b: "select count(*) from table_b", table_c: "select count(*) from table_c" }; async.map(sqls, function(item, callback) { connection.query(item, function(err, results) { callback(err, results[0]['count(*)']); }); }, function(err, results) { if(err) { console.log(err); } else { console.log(results); } });
运行结果:
{ table_a: 26, table_b: 3, table_c: 2 }
async.series按顺序执行多条任务
Async.js非常实用的一个功能就是流程控制。回到本文刚开始的那个开启事务执行Insert的例子,每一步都需要上一步执行成功后才能执行,很容易掉进回调大坑中。下面实用async.series函数来优化流程控制,让代码更优雅:[code]var title = 'It is a new post'; // 用于在posts插入成功后保存自动生成的ID var postId = null; // function数组,需要执行的任务列表,每个function都有一个参数callback函数并且要调用 var tasks = [function(callback) { // 开启事务 connection.beginTransaction(function(err) { callback(err); }); }, function(callback) { // 插入posts connection.query('INSERT INTO posts SET title=?', title, function(err, result) { postId = result.insertId; callback(err); }); }, function(callback) { // 插入log var log = 'Post ' + postId + ' added'; connection.query('INSERT INTO log SET data=?', log, function(err, result) { callback(err); }); }, function(callback) { // 提交事务 connection.commit(function(err) { callback(err); }); }]; async.series(tasks, function(err, results) { if(err) { console.log(err); connection.rollback(); // 发生错误事务回滚 } connection.end(); });
async.waterfall按顺序执行多条任务并且下一条任务可获取上一条任务的执行结果
上面使用async.series按顺序执行多条任务,但是很多情况下执行一个任务的时候需要用到上一条任务的相关数据,例如插入一条数据到posts表后,会自动生成ID,下一步插入日志会用到这个ID,如果使用async.series函数就需要定义一个变量var postId来存储这个ID,此时可使用async.waterfall来替代async.series。
[code]var title = 'It is a new post'; var tasks = [function(callback) { connection.beginTransaction(function(err) { callback(err); }); }, function(callback) { connection.query('INSERT INTO posts SET title=?', title, function(err, result) { callback(err, result.insertId); // 生成的ID会传给下一个任务 }); }, function(insertId, callback) { // 接收到上一条任务生成的ID var log = 'Post ' + insertId + ' added'; connection.query('INSERT INTO log SET data=?', log, function(err, result) { callback(err); }); }, function(callback) { connection.commit(function(err) { callback(err); }); }]; async.waterfall(tasks, function(err, results) { if(err) { console.log(err); connection.rollback(); // 发生错误事务回滚 } connection.end(); });
async.series获取多条SQL的结果
[code]// tasks是一个Object var tasks = { table_a: function(callback) { connection.query('select count(*) from table_a', function(err, result) { callback(err, result[0]['count(*)']); // 将结果传入callback }); }, table_b: function(callback) { connection.query('select count(*) from table_b', function(err, result) { callback(err, result[0]['count(*)']); }); }, table_c: function(callback) { connection.query('select count(*) from table_c', function (err, result) { callback(err, result[0]['count(*)']); }); } }; async.series(tasks, function(err, results) { if(err) { console.log(err); } else { console.log(results); } connection.end(); });
运行结果:
{ table_a: 26, table_b: 3, table_c: 2 }
以上是Async.js操作数据库经常会用到的一些例子,用上它的话就不再需要担心异步回调的坑了。Async.js不仅仅可以用于数据库操作,其他用到异步回调函数的地方都可以使用,例如文件读写等,本文只是通过数据库操作为例来介绍Async.js基本用法,它同样也可以运用到其他需要它的地方。除了上面介绍的几个函数外,Async.js还提供了一些其他实用的函数,可以参考文档灵活使用。
相关文章推荐
- 编译安装MySQL
- MySQLDump在使用之前一定要想到的事情 [转载]
- MySQL错误:2003-Can't connect to MySQL server on 'localhost' (10061)
- mysql修改字符编码
- mysql自定义循环函数
- mysql数据表中文乱码解决方案
- MySQL数据库加密与解密
- 在Window上安装Mysql
- Mysql数据库int(1)和tinyint(1)的区别&&扩展阅读
- Mysql经常使用命令
- mysql 分组取最新的一条记录(整条记录)
- 转-MySQL - Show Processlist 整理
- 配置mysql默认编码格式 && 解决Can't connect to local MySQL server through socket 问题
- 树莓派上的mysql数据库中文乱码问题
- mysql数据库批量操作
- MySQL functions, IF, CASE
- MYSQL-TIMESTAMP数据类型的默认值与自动更新问题
- Mysql数据库性能优化
- MySQL 报错 : too many connections
- 解决:Hibernate 向Mysql插入中文数据乱码,出现???