小小工具类 简化nodejs回调的烦恼
发布于 3年前 作者 chenliang 3288 次浏览

最近接触了nodejs服务器端编程,于是准备拿它与mongodb做个新项目。

因为有js的基础,上手起来很快,另外mongodb操作的也是json数据,所以功能做起来还算一帆风顺,但有个问题一直让我很恼火,就是数据处理的回调函数层层嵌套,而后面又落下长长的方法关闭尾巴,心里很是不爽。

由于回调函数是为了实现非阻塞编程,也是nodejs的最大亮点,没有回调则不可能有nodejs作为服务器端应用程序的角色。于是我开始琢磨能不能让层层嵌套的回调函数平行起来,再用js的函数变量串行化起来呢?

在一段时间的钻研与试验后,有了实现这个想法的工具类:

/*
[@author](/user/author) chenliang
[@param](/user/param) jobCtx a json object as context param
[@param](/user/param) callbackArray an array object of callback function
*/
var SeqJobCtx = module.exports = function(jobCtx,callbackArray) {
    this.ctx=jobCtx;
    this.callbacks=callbackArray;
    
    this.next=function(){
        var cbm=this.callbacks.shift();
        if(cbm!=null){
            cbm(this);
        }
    };
    
    this.start=function(){
        this.next();
    };
};

我且把它叫做SeqJobCtx类,是不是很小巧?下面来看看它有何本事:

var mongodb = require("mongodb");
//引入SeqJobCtx类
var SeqJobCtx=require("./SeqJobCtx");

console.log("start on :"+__dirname);
//连接mongodb
var serv = new mongodb.Server('localhost', 27017, {});
var dbManager = new mongodb.Db('db', serv);

//正式操作mongodb
dbManager.open(function (error, db) {
    if(error) throw error;
    var ctx=new SeqJobCtx({"db":db},[drop_capCol,create_capCol,query_capCol,closeDB]);
    ctx.start();
});

function create_capCol(seqCtx){
    var db=seqCtx.ctx.db;
    db.createCollection("capCol",{capped:true,size:1000000,max:100},function(err,coll){
        if(err){
            console.log("Create capCol failed:"+err);
        }else{
            console.log("Collection capCol created.");
        }
        coll.insert({"hello":"world"},function(err,rl){
            if(err){
                console.log("Insert failed:"+err);
            }else{
                console.log("Insert finished.");
            }
            seqCtx.next();              
        });
    });
}
function drop_capCol(seqCtx){
    var db=seqCtx.ctx.db;
    db.dropCollection("capCol",function(err){
        if(err){
            console.log("Drop capCol failed:"+err);
        }else{
            console.log("Drop capCol ok.");
        }
        seqCtx.next();
    });
}
function query_capCol(seqCtx){
    var db=seqCtx.ctx.db;
    db.collection("capCol",function(err,coll){
        coll.find().limit(5).toArray(function(err,array){
            console.log("array.length:"+array.length);
            for(var i=0;i<array.length;i++){
                console.log("result:"+JSON.stringify(array[i]));
            }
            seqCtx.next();
        });         
    });
}
function closeDB(seqCtx){
    var db=seqCtx.ctx.db;
    db.close();
    console.log("DB closed.");
    seqCtx.next();
}

相信大家还是能够很容易读懂的了,构造SeqJobCtx的第一个参数是个json对象,我传入db作为上下文参数,这个上下文参数在整个作业运行期间都是有效的;第二个参数是方法名参数数组,看到了吧对应着后面四个function,分别是删除collection,建立collection,查询collection,关闭数据库连接。

各个数据库操作步骤都有了自己的名字,长长的方法关闭尾巴不见了,一眼就能读懂整个作业运作,是不是觉着有点象配置工作流的感觉?这样一来,如果以后业务变更了,要把流程改为先建collection,查询,然后删除collection,关闭数据库,你只需要调换一下方法变量数组的顺序就是了,而且还强迫那些习惯了在阻塞式程序中,一个方法写个几百上千行代码的开发人员优化结构,提高代码可读性。

最后调用SeqJobCtx的start方法,让它开始为你工作吧!

差点忘了交待两个关键步骤,就是每个被调方法变量的方法都接收一个参数,这个参数其实是SeqJobCtx本身,这个参数为你提供了上下文,可以看到数据库连接就是从这个上下文中得到的: var db=seqCtx.ctx.db; 另外,不要忘了在回调结束的时候调用SeqJobCtx的next方法,这样整个作业才能够串行化起来。

希望这个小小的工具类能够为你的nodejs工作变得舒心顺畅,也相信你很快能够掌握它的工作原理,做出自己功能强大的工具来。

10 回复

可以看下jscex或eventproxy

也可以看下 https://github.com/creationix/step 精巧而又犀利 你可以参考参考然后改进自己噢

呵呵,之前确实不知道这些库,大致看了下,jscex好像是要编译的?eventproxy是异步调用的,而我的是要解决同步的。 step和我的比较相似,但为什么业务方法要放在step()里面了,没看大明白,这样的实现方法重用性如何,调度起来方便性如何。

mongodb可以选择一些上层的包装: mongoskin, mongoose等

建议使用Jscex,参考老赵写的《使用Jscex改进NodeClub》系列文章 http://blog.zhaojie.me/

这个思路不错,如果可以实现并行执行就更好了。 大概意思是,假设一个情景:A、B、C函数,A、B互不依赖,但C依赖A、B。那么执行A后等待回调的时候就可以执行B,A,B就类似并行执行,再串行执行C。

可以参考一下jquery.deferred的API:http://api.jquery.com/category/deferred-object/ 这里有类似的实现。

async更强大一些~

自己写的一个类似的,除了线性以外,还支持并行,和一系列其他小功能

http://etui.github.com/Rytm/Rytm.html

支持,100+行的代码,在同等模块中算佼佼者了。

还是喜欢简洁小巧的combo

回到顶部