异步nodejs代码的同步样子写法样例

js的异步嵌套太深代码将不好看。尤其在用node的时候这种情况会大量出现。

这里用node连接redis,做一个用户注册的简单例子来说明。例如用redis做存储。我们设置一个String类型的key, seq:user这个作为自增主键ID,set类型allUser存放所有用户ID,和set类型allUserName存放所有用户名。

代码示例如下

 var redis = require("redis"),
db = redis.createClient(6379, "127.0.0.1");
db.on("connect", function() {
main();
});
function main() {
insertTest();
}
var insertTest = function() {
db.incr("seq:user", function(err, userID) {
if (err) {
console.log(err);
} else {
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");
});
}
});
};

逻辑很简单,先通过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库要简单方便。

异步nodejs代码的同步样子写法样例的更多相关文章

  1. mocha框架下,异步测试代码错误造成的问题----用例超时错误

    今天用抹茶(mocha)做个测试,发现有一个测试项目总是超时: describe("DbFactory functions",function(){ it("query ...

  2. Python代码转c#部分参考样例

    最近在做一部分Pyhton代码转c#代码的工作,以下案例亲自都测试过,现整理出来希望对有帮助的同学提供参考: Python | C# *:first-child{margin-top:0 !impor ...

  3. C++ Primer中文本查询演示样例Query的实现

    近期在看C++ Primer复习C++的语法,看到书中15.9章中的文本查询演示样例时,认为设计得非常不错,于是便动手照着实现了一个,改动了非常久最终执行成功了,从中也学习到了非常多的语法.以下把实现 ...

  4. Python Web框架Tornado的异步处理代码演示样例

    1. What is Tornado Tornado是一个轻量级但高性能的Python web框架,与还有一个流行的Python web框架Django相比.tornado不提供操作数据库的ORM接口 ...

  5. javascript es6 Promise 异步同步的写法(史上最简单的教程了)

    1 来个简单的例子 var p = new Promise(function(resolve, reject){ //做一些异步操作 setTimeout(function(){ console.lo ...

  6. Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G

    code&monkey   Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...

  7. java 线程、线程池基本应用演示样例代码回想

    java 线程.线程池基本应用演示样例代码回想 package org.rui.thread; /** * 定义任务 * * @author lenovo * */ public class Lift ...

  8. 18.1利用socket .io 实现 editor间代码的同步

    首先,我们想实现在同一个页面editor 大家同时编辑 同步 所以能 我们需要这个url 作为我们 session id 或者叫聊天室的roomid 或者 反正就是保证他们在同一个list里面 就是 ...

  9. 10分钟理解Android数据库的创建与使用(附具体解释和演示样例代码)

    1.Android数据库简单介绍. Android系统的framework层集成了Sqlite3数据库.我们知道Sqlite3是一种轻量级的高效存储的数据库. Sqlite数据库具有以下长处: (1) ...

随机推荐

  1. (转)Go语言核心36讲之Go语言学习路线

  2. (C语言逻辑运算符!)&&两种定义字符串的方法&&局部变量、全局变量&&内部函数、外部函数。(新手基础知识备忘录)

    (一)      四个逻辑运算符:!(逻辑非) ||(或) &&(与) ^ (异或) 位运算:&(与) |(位或) (二)        如何定义字符串: 1,字符串常量   ...

  3. 网络编程- 解决黏包现象方案二之struct模块(七)

    上面利用struct模块与方案一比较,减少一次发送和接收请求,因为方案一无法知道client端发送内容的长度到底有多长需要和接收OK.多一次请求防止黏包,减少网络延迟

  4. 后台微信开发入口+关键字 回复等 关注公众号回复 注意获取随机Token 微信的对接校验Token保存到数据库的只是做第一次的校验 其他对微信公众号的操作是去缓存中获取7200S的随机Token

    package com.epalmpay.controller.apiweixin; import com.epalmpay.commom.BaseController;import com.epal ...

  5. AngularJs学习笔记--Injecting Services Into Controllers

    原版地址:http://docs.angularjs.org/guide/dev_guide.services.injecting_controllers 把service当作被依赖的资源加载到con ...

  6. unittest实现批量处理测试集

    批量执行测试集 #coding=utf-8 from selenium import webdriver from selenium.webdriver.common.by import By fro ...

  7. sencha touch SortableList 的使用

    转: sencha touch 2.3 多了一个  SortableList plugin, 可实现 list item 的拖动交换 Ext.require('Ext.plugin.SortableL ...

  8. 《图解http协议》之HTTPs学习笔记

    对于IP协议,并不陌生.TP协议是TCP/IP协议簇中的核心协议,也是TCP/IP的载体.所有的TCP,UDP,ICMP及IGMP数据都以IP数据报格式传输.IP提供不可靠的,无连接的数据传送服务.I ...

  9. 2.VS2012为创建的类添加注释的模板

    在项目中给类添加注释的优点: 1.方便查看这个类是为了那些功能 2.是成员小组中的谁负责编写的 根据自己的vs的安装路径找到类模板的位置:D:\Program Files (x86)\Microsof ...

  10. golang学习之闭包

    匿名函数不能够独立存在,但可以被赋值于某个变量,即保存函数的地址到变量中:fplus := func(x, y int) int { return x + y },然后通过变量名对函数进行调用:fpl ...