zoukankan      html  css  js  c++  java
  • 异步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");
            }
        });
    };
    用了redis的multi不仅可以保证插入代码的顺序执行,还能较少网络IO提高性能。但是需要注意的是redis没有事务支持不能回滚,如果multi里面的语句任意一条出错,后面的插入依然照常插入。
    例如对一个本是String类型的key 做 lpush 命令则会出错。(lpush seq:user "11") 。 但是redis的官方解释说这些错误可以在客户端提交之前通过检查就可以避免。所以只需要也只能要求我们的代码没问题,才能保证保存的数据都是干净的。
     
    连续插入可以使用multi解决。但是像一些判断用户有无之类的逻辑需要用其他方法。
    例如,注册新用户的逻辑是,1,先判断用户名重复否,2.不重复则取得一个自增ID,3.最后通过自增的ID作为用户ID,来插入基本数据。
     
    后面两个函数有了,这里新加一个判断用户是否重复的函数。代码可能是这样的
    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库要简单方便。

  • 相关阅读:
    uiautomator2自动化测试
    APP性能测试(电池电量、流量、CPU、内存)
    python虚拟环境搭建
    unittest报错(return self._request(command_info[0], url, body=data))
    python pip错误处理
    adb 实用命令
    大数据学习(一)
    windows 下Virtualenv搭建mysql环境
    python奇怪毛病集合
    python作业
  • 原文地址:https://www.cnblogs.com/laozhbook/p/nodejs_async.html
Copyright © 2011-2022 走看看