Nodejs 实现同步的方法

来源:互联网 发布:最短路径优先算法 编辑:程序博客网 时间:2024/06/06 18:04

转载自Nodejs: 异步?!我要同步!!!  

同时给出http://www.cnblogs.com/lengyuhong/archive/2011/12/22/2290711.html供快速参考


Nodejs是一个事件驱动、异步非阻塞的Javascript平台。异步编程模型是它的主要特色,所以nodejs众多的第三方模块都提供了异步api。

这种方式有很多优点,但在某些情况下,会造成以下问题:

  1. 代码多层嵌套,难写难读
  2. 控制异步调用的先后顺序,很麻烦

具体如何这里就不多说了,相信每个使用过的人都深有感触,不然怎么会有那么多的模块存在的目的就是为了改善这一点呢?

  1. http://altjs.org/ 见Synchronous to Asynchronous (CPS)一节
  2. https://github.com/joyent/node/wiki/modules#wiki-async-flow

这些通过三种方式来实现:

  1. 通过nodejs的本身设施实现,如async,q,node-seq,step等
  2. 通过语言扩展,定义一些特有的关键字如await等,如tamejs,jscex。可惜多了一步编译
  3. 通过node-fibers项目提供的纤程,如fibrous,common-node,streamline等

经过近两天的查看与试用,我对其中几个比较感兴趣。我的标准是:

  1. 示例代码易读、易理解
  2. 在不同的环境中都能稳定运行(如mocha中)
  3. 能方便与已有模块的异步api一起使用

下面一一介绍。

Tamejs

https://github.com/maxtaco/tamejs

tamejs不是纯js,因为它增加了两个特有的东西:await/defer。通常把文件保存为.tjs,在运行前需要先编译为.js(也可以通过require某些库在运行期进行)。这里给一个代码示例:

for (var i = 0; i < 10; i++) { 
    await { setTimeout (defer (), 100); } 
    console.log ("hello"); 
}

 

代码看起来相当简洁,为了这个,我宁愿多一个编译步骤!

看看它转换的js代码是什么样的:

var tame = require(‘tamejs’).runtime; 
var __tame_defer_cb = null; 
var __tame_fn_0 = function (__tame_k) { 
    tame.setActiveCb (__tame_defer_cb); 
    var __tame_k_implicit = {}; 
    var i = 0; 
    var __tame_fn_1 = function (__tame_k) { 
        tame.setActiveCb (__tame_defer_cb); 
        var __tame_fn_2 = function (__tame_k) { 
            tame.setActiveCb (__tame_defer_cb); 
            i ++ 
            tame.callChain([__tame_fn_1, __tame_k]); 
            tame.setActiveCb (null); 
        }; 
        __tame_k_implicit.k_break = __tame_k; 
        __tame_k_implicit.k_continue = function() { __tame_fn_2(__tame_k); }; 
        if (i < 10) { 
            var __tame_fn_3 = function (__tame_k) { 
                tame.setActiveCb (__tame_defer_cb); 
                var __tame_fn_4 = function (__tame_k) { 
                    tame.setActiveCb (__tame_defer_cb); 
                    var __tame_defers = new tame.Deferrals (__tame_k); 
                    var __tame_fn_5 = function (__tame_k) { 
                        tame.setActiveCb (__tame_defer_cb); 
                        setTimeout ( 
                        __tame_defers.defer ( { 
                            parent_cb : __tame_defer_cb, 
                            line : 2, 
                            file : "d.tjs" 
                        } ) 
                        , 100 ) ; 
                        tame.callChain([__tame_k]); 
                        tame.setActiveCb (null); 
                    }; 
                    __tame_fn_5(tame.end); 
                    __tame_defers._fulfill(); 
                    tame.setActiveCb (null); 
                }; 
                var __tame_fn_6 = function (__tame_k) { 
                    tame.setActiveCb (__tame_defer_cb); 
                    console . log ( "hello" ) ; 
                    tame.callChain([__tame_k]); 
                    tame.setActiveCb (null); 
                }; 
                tame.callChain([__tame_fn_4, __tame_fn_6, __tame_k]); 
                tame.setActiveCb (null); 
            }; 
            tame.callChain([__tame_fn_3, __tame_fn_2, __tame_k]); 
        } else { 
            tame.callChain([__tame_k]); 
        } 
        tame.setActiveCb (null); 
    }; 
    tame.callChain([__tame_fn_1, __tame_k]); 
    tame.setActiveCb (null); 
}; 
__tame_fn_0 (tame.end);

 

看起来还是有点吓人的,不容易啊不容易。

非常可惜的是,在mocha中无法正常使用。看下面这段代码:

require(‘should’);

function inc(n, callback) { 
  setTimeout(function() { 
    console.log(‘### inc: ‘ + n); 
    callback(n+1); 
  }, 1000); 
};

describe(‘test’, function(){ 
  it(‘show ok with tamejs’, function(){ 
     console.log(‘### testing …’); 
     var result; 
     await { inc(1, defer(result)); } 
     console.log(‘result: ‘ + result); 
     result.should.equal(3); 
  }); 
});

 

不知为什么,提示测试通过,实际上inc函数完全没有运行。

提了个bug: https://github.com/maxtaco/tamejs/issues/30,希望能早日解决。

Common Node

https://github.com/olegp/common-node

Common node基于node-fibers,提供了同步风格的代码,但在内部利用了纤程,不会阻塞主线程。既可以使用同步风格的代码,又不会影响性能,看起来很不错。

这里有一个性能对比图,可见common-node的同步风格,对于性能的影响还是很小的:

image

common-node的定位应该与node是相同的,但在编程风格上正好相反,它提倡使用“同步”。也因此,对于已有的异步api的模块,它不能很好地直接使用,需要专门为它提供一个修改版。从README中可以看到,目前已经有一些项目支持common-node。可惜相比nodejs庞大的模块库来说,还是可怜了,所以common-node的发展前景还是有些不明朗。

当支持它的模块更多一些之后,也许会有更多人来尝试它的。

Fibrous

(修改:在实际使用中,发现该库与很多其它的库有冲突,出现各种各样的错误,故不再推荐。现在使用async替代,见下段)

https://github.com/goodeggs/fibrous

Fibrous将给每个对象增加一个sync和feature属性,给原有的各异步方法提供一个同步版。因为它也基于node-fibers,所以这个同步函数实际上将在一个纤程中执行,不会阻塞主线程。

它的代码写起来比较简洁,看示例:

var fibrous = require(‘fibrous’); 
var crypto = require(‘crypto’);

var random = fibrous(function(length) { 
    var buf = crypto.sync.randomBytes(length); 
    return buf.toString(‘hex’); 
});

random(4,function(err, result) { 
    console.log(result); 
});

 

如果我们的代码被fibrous()包着,则可在内部调用其它对象的.sync中的某方法(该方法是原异步方法的同步版本),不需要传入回调函数,直接以返回值形式拿到。

如果我们的代码没有被fibrous包着,则还得老老实实传回调。但这不是大问题,因为大多数情况下,我们都可以用fibrous包起来。

再看看它与mocha结合使用:

require('should');var fibrous = require('fibrous');function inc(n, callback) {    setTimeout(function() {        console.log('### inc: ' + n);        callback(null, n+1);    }, 1000);};describe('test', function(){    it('show ok with tamejs', fibrous(function(){        var x = inc.sync.call(null, 5);        console.log('x:'+x);    }));});

注意it(‘..’, fibrous(..)),可以直接把测试部分给包起来,内部就可以使用inc的sync版了。执行这个测试,将正确打印出:

x:6

使用fibrous,虽然对js中的对象有些侵入(增加了sync和futrue属性),但是很多时候,可以让我们的代码更加简洁,这个代价是我完全可以承受的。我将在以后更多的情况下使用它,看看会不会有其它问题。

暂时不用再纠结于异步同步的问题了,因为所有能试的方法我基本上都试了一遍,如果连fibrous也不行,那我也就只能放弃,强迫自己去写回调了。

Async

https://github.com/caolan/async

前面说中了,fibrous在使用中,与很多库有冲突,无奈移除,只好再硬着头皮写嵌套。期间纠结得想换语言,直到用了async。

async设计得巧妙又周到,考虑到了很多场景,能够很大程度上消除嵌套。比如说三种常见的情况:

  1. 多个异步函数并行执行,并且在全部执行完以后进行某个操作:parallel
  2. 多个异步函数依次执行,函数之间没有数据传递:series
  3. 多个异步函数嵌套执行,每个外层都要把数据传给内层:waterfall

另外还有7种:)

还有一种常见场景,需要对一个数组中的每一个元素进行异步操作,这时又提供了十种操作:

  1. forEach
  2. map
  3. filter
  4. reject
  5. reduce
  6. detect
  7. sortBy
  8. some
  9. every
  10. concat

详细的使用方法,我正在边学习边写示例,发到github上:https://github.com/freewind/async_demo


原创粉丝点击