异步nodejs代码的同步样子写法样例
异步nodejs代码的同步样子写法样例
js的异步嵌套太深代码将不好看。尤其在用node的时候这种情况会大量出现。
这里用node连接redis,做一个用户注册的简单例子来说明。例如用redis做存储。我们设置一个String类型的key, seq:user这个作为自增主键ID,set类型allUser存放所有用户ID,和set类型allUserName存放所有用户名。
代码示例如下
1 var redis = require("redis"), 2 db = redis.createClient(6379, "127.0.0.1"); 3 db.on("connect", function() { 4 main(); 5 }); 6 function main() { 7 insertTest(); 8 } 9 var insertTest = function() { 10 db.incr("seq:user", function(err, userID) { 11 if (err) { 12 console.log(err); 13 } else { 14 var username = "username001"; 15 db.set("user:" + userID + ":username", username, redis.print); 16 db.set("user:" + userID + ":passwd", "111111", redis.print); 17 db.set("username:" + username + ":uid", userID, redis.print); 18 db.sadd("allUserName", username, redis.print); 19 db.sadd("allUser", userID, function() { 20 console.log("OK"); 21 }); 22 } 23 }); 24 };
逻辑很简单,先通过incr seq:user拿到自增的ID成功之后,下一步一个个保持字段信息和列表数据。
上面代码也可以改写成这个样子
var getLastUserID = function(callback) { db.incr("seq:user", function(err, userID) { if (!err) { callback(userID); } }); }; var insertUser = function(userID) { var username = "username001"; db.set("user:" + userID + ":username", username, redis.print); db.set("user:" + userID + ":passwd", "111111", redis.print); db.set("username:" + username + ":uid", userID, redis.print); db.sadd("allUserName", username, redis.print); db.sadd("allUser", userID, function() { console.log("OK"); }); }; var insertTest = function() { getLastUserID(insertUser); };
首先要保证的是insertUser函数里面所有的插入语句顺序OK。因为我们只在最后一此插入的回调中做业务处理。中间插入语句有没有成功程序无法知晓。还有连续的同服务器交互增加了不少网络IO量。
这里的改进,使用redis的pipeline。
var insertUser = function(userID) { var username = "username001"; var multi = db.multi(); multi.set("user:" + userID + ":username", username); multi.set("user:" + userID + ":passwd", "111111"); multi.set("username:" + username + ":uid", userID); multi.sadd("allUser", userID); multi.sadd("allUserName", username); multi.exec(function(err, replies) { if (!err) { console.log("OK"); } }); };
var isUserNameExists = function(username, callback1, callback2) { db.sismember("allUserName", username, function(err, isMember) { if (!err && !isMember) { callback1(username, callback2); } }); }; var getLastUserID = function(username, callback) { db.incr("seq:user", function(err, userID) { if (!err) { callback(username, userID); } }); }; var insertUser = function(username, userID) { var multi = db.multi(); multi.set("user:" + userID + ":username", username); multi.set("user:" + userID + ":passwd", "111111"); multi.set("username:" + username + ":uid", userID); multi.sadd("allUser", userID); multi.sadd("allUserName", username); multi.exec(function(err, replies) { if (!err) { console.log("OK"); } }); }; var insertTest = function() { var username = "username003"; isUserNameExists(username, getLastUserID, insertUser); };
或者是用匿名函数的嵌套,写在一个地方
db.sismember("allUserName", username, function(err, isMember) { if (!err) { if (!isMember) { db.incr("seq:user", function(err, userID) { if (!err) { var multi = db.multi(); multi.set("user:" + userID + ":username", username); multi.set("user:" + userID + ":passwd", passwd); multi.set("username:" + username + ":uid", userID); multi.sadd("allUser", userID); multi.sadd("allUserName", username); multi.exec(function(err, replies) { if (!err) { console.log("OK"); } }); } }); } } });
两个方式阅读起来都不方便。更何况我们的示例代码逻辑并不是十分复杂,还没有加异常处理了。即便这样也是很难阅读的。
js的promise标准可以做到类似同步代码一些的写法。还有一些其他的js库也提供一些相应的同步写法。
这里介绍个叫async的库。地址在https://github.com/caolan/async
下载安装好后引入
var async = require("async");
利用async的waterfall可以指定一组函数的执行顺序,并且可以根据中间状态做流程控制。
上面的代码就可以改写成如下样子
var insertTest = function() { var username = "username005"; async.waterfall([ function(callback) { db.sismember("allUserName", username, callback); }, function(isMember, callback) { if (!isMember) { db.incr("seq:user", callback); } else { callback(new Error("username has exist"), null); } }, function(userID, callback) { var multi = db.multi(); multi.set("user:" + userID + ":username", username); multi.set("user:" + userID + ":passwd", "111111"); multi.set("username:" + username + ":uid", userID); multi.sadd("allUser", userID); multi.sadd("allUserName", username); multi.exec(function(err, replies) { if (!err) { callback(null, "OK"); } else { callback(new Error("save data fail"), null); } }); } ], function (err, result) { console.log(err); console.log(result); }); };
它会按照定义好的函数列表挨个执行,并且上一个函数执行的结果很方便地传入到下一个函数中做判断。上例代码中因为js约定的机制,回调第一个参数err,第二个是正确结果。所以传递的过程都不需要自己写。
这样改写之后不光看起来清楚,流程控制起来也更加方便。例如如果用户名重复的错误,或者是保存数据失败的错误发生了,或者是全部调用成功了。都可以在最后的统一结果处理函数中处理掉。
使用async.waterfall做流程控制个人感觉比用promise库要简单方便。