Async.js解決Node.js操作MySQL的回調大坑
因為JavaScript語言異步特性。在使用Node.js運行非常多操作時都會使用到回調函數,當中就包含訪問數據庫。假設代碼中的業務邏輯略微復雜一點,回調一層層嵌套。那麽代碼非常easy進入Callback Hell,不管對寫代碼的人還是閱讀代碼的人,都是精神上的折磨。
比如對MySQL的一個事務操作,插入一條posts並插入一條log:
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函數:
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函數:
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,更適合批量運行查詢語句並返回結果:
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函數簡化成這樣:
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的樣例。每一步都須要上一步運行成功後才幹運行,非常easy掉進回調大坑中。
以下有用async.series函數來優化流程控制,讓代碼更優雅:
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。
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的結果
// 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還提供了一些其它有用的函數,能夠參考文檔靈活使用。
Async.js解決Node.js操作MySQL的回調大坑