Freewind @ Thoughtworks scala java javascript dart 工具 编程实践 月结 math python english [comments admin] [feed]

(2012-05-26) 止步于nodejs的异步

广告: 云梯:翻墙vpn (省10元) 土行孙:科研用户翻墙http proxy (有优惠)

学习nodejs一个月之后,终于还是败在了nodejs的异步编程模型上。

我对nodejs的其它都还是比较满意的:活跃的社区,很多有创意的项目,node本身无比快速的启动,以及写js代码时那种虽然不踏实但相当放松的感觉,都让我很舒服。

除了异步。

nodejs的特点是“单线程、基于事件非阻塞”,对于各种耗时操作,比如IO/数据库之类,都通过异步api调用,传个回调进去。让node在后台通过其它线程处理,完了以后调用我们的回调函数,把错误或者数据传进来。这也可算是一件好事,因为我们的代码运行于单线程,不需要考虑线程竞争、同步的问题,但是带来的问题也是相当麻烦:每调用一次异步,就要多一次嵌套,代码复杂点,轻轻松松五六层。这还是小事,更麻烦的是,错误处理变麻烦了(不能用try-catch),算法逻辑分散了(东一块西一块),当需要考虑某些操作并行或顺序执行时,也非常难以控制。

期间纠结了几次,实在不愿直面散乱的代码,想换到其它同步风格的语言。好在发现了两个比较好的工具,以为解决了我的问题。

  1. async 一个纯nodejs库,提供了控制流程和集合处理的方法,可以让我们大大减化操作
  2. streamlinejs 通过引入一个下划线作为callback的占位符,让我们以同步风格来调用异步代码,比async更简洁。缺点是需要手动转换为js代码,或者调用命令行时增加参数

这里只列出了两个,但我实际上试用了几乎所有能找到的模块(十几个最少)。所以目前市面上基本很难找到比它们两个更好的了。如果有的话,请向我推荐。

这两个相比,我更加偏爱后者,因为它可以让我们的代码简洁到无法再简洁的程度。可读性非常高,并且与mocha的结合也很完美。我本以为可以安心地继续写nodejs了。

然而今天在把之前用async写的一些代码转换为streamlinejs时,发现了一个严重的问题。有一些逻辑稍复杂的代码(先批量清空数据库,再指插入新数据),使用streamlinejs改写后,运行总不对。明明是想先清空后插入,不知怎么就变成先插入后清空了。其文档很简单,示例也少,找不到资料。想看一下转换后的js代码,相当复杂:https://gist.github.com/2788457,如同经过混淆。想在代码中增加一些调试信息也不行,因为那个下划线的用法是定死的,不能先写一个自定义的callback再在里面调用下划线方法。

经过5个小时的调试,我终于放弃了。回想这一个月的学习过程,太多的时间花在nodejs的异步风格的转变,以及各种模块之间的配合上。因为nodejs几乎所有的模块都沿袭了异步风格,如果想用同步库来做,经常会遇到各个模块之间的不兼容问题。

在我决定离开nodejs之前,我还挣扎了一下,看了一眼ringojs。它是一个运行于jvm上的与nodejs相似的平台,使用rhino引擎来解释js代码,符合commonjs规范,并且是同步风格的。可惜的是,我想用的express和mongoose都是基于nodejs的,调用了大量的nodejs的api,不能直接在ringojs上运行。在ringojs上可以用java的库,但我实在不想用了。

nodejs不可怕,可怕的是围绕它建立起来的6000多个模块。因为这一点,node-fibers和ringojs都不是它的对手,虽然它们写出来的代码会让人更舒服一些。

下一步,先转向dart,不行再ruby。继续折腾。

comments powered by Disqus