发布一个异步控制流的node模块xchain
发布于 2年前 作者 thynson 4658 次浏览

[xchain]是用于简化异步控制流的库,已经在npm上注册了。至于有什么用,用过[Step]的人会明白,但因为我觉得[Step]的异常处理方式是存在一定问题的。这个库的接口设计经过了很久的思考,应该是很简单且实用的。

用代码简要地说明:

var xchain = require('xchain');
var util = require('util');

xchain(
  // 在这个圆括号内的作为xchain的参数的两个函数
  // 是并行执行的。
  function(next) {
    setTimeout(function(){
      next.foo = 1;
      util.log('next.foo completed');
      next(); // 表示这个异步操作完成
    }, 1000); // 模拟一个用时1秒的异步操作
  },
  function(next) {
    setTimeout(function() {
      next.bar = 2;
      util.log('next.bar completed');
      next();
    }, 2000); // 模拟一个用时2秒的异步操作
  }
)(
  // xchain()返回的也是一个函数,用法和上面相同,
  // 用链式的表达式来表示依赖关系
  function(next) {
    util.log('foo=', next.foo);
    util.log('bar=', next.bar);
    throw { error : 1 }; // 抛出的异常会被传给run方法的回调捕获
  },
  function(next) {
    next(); 
  }
)(
  function(next) {
    throw { error : 2 };
  }
)(
  function(next) {
    // 不会执行到此处,自行揣摩传给run方法的回调
  }
).run(function(err, next) {
  if (err.error == 1)
    next(); // 模拟一个可以恢复的错误
  // 其他错误不处理
});

###简单的总结

  • 通过链式表达式的形式描述异步操作的依赖关系和并行关系,应该是比较简洁而清晰的。
  • 异步操作完成时将next当作函数来调用,以通知xchain当前异步操作完成,当所有并行的操作完成是,xchain会执行下一个(批)异步操作。
  • 为了方便数据的通信,也可以将next当作普通的对象使用(如有必要,也可以使用闭包)。
  • 在构建好异步控制流之后,调用run()方法开始从上而下执行。可以给run()方法传递一个异常处理函数,可以在异常处理函数中恢复控制流,或者中断控制流的执行(如不提供异常处理函数,一旦发生异常,默认按照依赖关系停止异步控制流的执行)。

此外,欢迎fork,comment和patch。 [xchain]: http://github.com/thynson/xchain [Step]: http://github.com/creationix/step

54 回复

能简单介绍下你这个模块的工作流程吗?

感觉很高深,我想知道这个和老赵的wind.js还有eventproxy是什么关系呢?

这个不合适吧!用setTimeout来模拟执行时间不大可取! 毕竟执行一段代码你不知道是1秒还是2秒还是0.2秒、甚至20ms! 当然这是解决异步的一种方法,但是个人觉得不怎么靠谱!

汗,这是作者的示例,不是实现机制~ settimeout只是用来模拟一个异步操作

@snoopy 额~没仔细看!

我不喜欢老赵的wind.js那样的,用编译的方式来达到异步,在js中,就应该尽量用异步的思想去思考问题。而eventproxy的接口太繁琐,完全可以用链式表达式将各个异步操作的依赖关系表示清楚。

@jeffz 相反,我觉得将思维从同步方式转换到异步方式才是进化。对于并发的处理,恰恰是同步方式的弱项而是异步的强项。很多人认为异步会把事情搞砸,将逻辑搞乱,我认为不是异步的问题,而是缺少一种适用异步的描述方式,在这一点我深受[Step]启发(我认为其唯一的缺陷是不应将异常传递下去),但力求更好。 [step]:http://github.com/creationix/step

github上的源代码吧,应该不是很难理解。

还没去看他的代码,但我感觉应该是使用递归实现的

@jeffz 这几天遇到有困惑的地方, JS 函数式的写法不能全局共享 db 和 socket 对象了, wind 当时是不是说对付这样的问题?

handle = (db, s, cb) ->
  para = ''
  # db.xxxxxxx
  # s.emit xxxxxxx
  cb(para)
mongodb.connect url, (err, db) ->
  io.sockets.on 'connecttion', (s) ->
    s.emit 'ready', 'you are connected'
    name = ''
    s.on 'name', (str) -> name = str
    handle db, s, (str) -> name = str

@thynson 简单看了下,原理还是 eventproxy 的,api比它要好~值得推荐啊

@thynson 并发跟同步异步真心没啥关系……再说你现在这个是流程表达辅助用的类库,本来就是语言同步已经解决了一万遍的问题,只要你是希望解决这方面问题,就表明你没在进化……

@jiyinyiyong 没懂你是想做啥,如果是说异步调用时上下文保留的问题的话,那么Wind.js的确可以帮你很多……

@jeffz 在我看来从同步思维转向异步思维才是进化(针对您的幻灯片中的观点)。因此我并没有进化,而是node.js相对于其他很多语言来说进化了。而我所带来的仅仅是流程控制库更好的易用性,和更好的错误处理。在我的理解来看,wind.js就是让程序员用同步的方式编写能够异步执行的库,这个理解偏差不会太远吧?或许对于刚从其他语言转来的程序员、迁移现有的项目等有所帮助,但我认为要更好的使用node.js,则应该改造自己思维。我的库面向的也是完成了这些转变的程序员。

@thynson 刚才网页没响应了,一下n连发

@thynson 但是,我遇到事实却是,用回调的方式解决异步的问题非常痛苦 javascript根本没有异步可用的流程控制语句

@jeffz 我曾经也尝试着开发一个异步辅助类库Promise,但是开发开发着发现, 我的方向在往windjs方向靠拢,就快变成重复发明轮子了 而且发现wind还是挺优秀,轻量级,可控的,于是我果断 果断用windjs重写了我整个项目,

@thynson 能说一下step存在什么样的问题吗? 我原来的在前端需要异步处理时,是使用jquery.queue将所有异步处理串行化。但Node中不能使用jquery.queue,现在也在找怎样解决异步处理的问题。

var Step = require('step');
var con = require('console');

Step(
    function() {
        var next1 = this.parallel();
        var next2 = this.parallel();
        setTimeout(function() {
            next1({ error : 1}, 5);
        }, 100);
        setTimeout(function() {
            next2({ error : 2}, 20);
        }, 200);
    },
    function(err, a, b) {
        con.log(err);
        con.log(a);
        con.log(b);
    }
)

第一个异常{error : 1 }会被覆盖掉.

var xchain = require('xchain');
var con = require('console');

xchain(
  function(next) {
    setTimeout(function() {
      next({error : 1}); //异步调用中异常,不能用throw
    }, 100);
  }, 
  function(next) {
    next({error : 2});// 等价于 throw {error : 2};
  },
  function(next) {
    throw {error : 3};//等价于 next({error : 3});
  }
)(
  function(next) {
    
  }
).run(function(err, next) {
  con.log(err);
  next();
});

传给run方法的回调针对不同的异常会被调用三次

@thynson 哦,明白了,谢谢哦。这样偶也能放心 大胆不用step了。、

nodejs的技术欢迎加群聊262658247

想了解一下你的库和async库中的waterfall有什么差异?

想知道这样的代码用xchain有什么优势, 如果这个例子不能说明问题,能不能举个体现xchain优势的例子, 老赵看到了也说说wind的?

arr = [1,2,3,4,5,6,7,8,9];

async.reduce(arr, 0, function(result, item, cb){
    cb(item > 5 ? 'stopped on '+item : null , result + item);
}, function(err, result){
    console.log('reduce', err, result);
});

async.waterfall([
    function(cb){
        cb(null, arr.map(function(item){
            return item + 1;
        }))
    },
    function(result, cb){
        if(result[0] != 2)
            cb(null, result.map(function(item){
                return item + 1;
            }))
        else
            cb('stopped on step 2', result);
    },
    function(result, cb){
        cb(null, result.map(function(item){
            return item + 1;
        }))
    }
], function(err, result){
    console.log('waterfall', err, result);
})

另外给回帖功能提几个建议:

  • 提交了以后自己干嘛不能修改?
  • 能@某人就好了,和微博一样,github上的回复好像也有这种功能,就是引起某人注意

async.reduce和这个不是一个东西。 async.waterfall是完全串行的,目的也和xchain不一样,只有async.auto才有可比性。

@thynson 改造自己思维……什么时候程序员去迎合工具变成一件光荣的事情或是义务了?迁就语言的限制并无不可,但因为无法反抗就开始享受就太悲哀了……

没懂reduce要干嘛的……waterfall不就是串行嘛,用Wind.js写就是:

var r1 = arr.map(function(item) { return item + 1; }));
if (r1[0] == 2) throw 'stopped on step 2';

var r2 = r1.map(function(item) { return item + 1; });
var r3 = r2.map(function(item) { return item + 1; });

return r3;

不过你异步的点在哪里?

@jeffz 首先此非迎合工具,但是如果不适合于自己,为何不换一个工具?在你我看来同一个工具的价值不同,各取所需便是。 其次,对于一些人来说,强制的异步是一种限制,但对我和另一些人来说,这反而是其苦苦追寻的东西。

api确实比eventproxy要好。。

@thynson 不要混淆“异步”和“回调”,“串行”。异步是一种性质,不要把异步跟回调等同一起来,你苦苦追寻的是异步,而不是回调。普通的同步程序也可以写成回调的,但你不会,因为这么写会很难读。异步也是一个道理,只不过很多时候只能回调,但可以不回调的时候,再说“回调是我苦苦追求的东西”就很蛋疼了……采用事件驱动也好,采用Async或Step都行,但理由不能是“异步是一种文化一种进步,要迎合异步思维”,不能说这是的“苦苦追求”的东西,很奇怪。

@jeffz 好吧,我所需要的正是这种“闭包+匿名函数”来驱动的异步,想必您没有接触过函数式编程语言,对于Lisp,Scala,Erlang等语言的用户而言,“闭包和匿名函数”可是思考问题的基石。既然视角不同,多说无益,但还请你承认,js首先是一门函数式编程语言。

@thynson 这又关函数式编程什么事情了?Wind.js的基础理论Monad源自Haskell,Wind.js的启发来自于F#,说起来Wind.js那才叫一个函数式呢。还有啊,评价人或物之前要去做点基本了解对不,我忽然又没有接触过函数式编程语言了情何以堪……

@jeffz 好吧,这么说是我的逻辑错误(连带地把js也归为非函数式编程语言);Wind.js的实现也确实少不了函数式编程的理论,在评价前未调研也是因为我的偏见。 既然如此,那我就不带偏见的论述: 为什么我说“回调式的异步是我一直苦苦追寻的”,因为从我的经验来说,回调式的异步更有扩展性,你可以传入多个回调,而这些回调可以毫无关系,分散在工程各处;但函数返回值只有一处能够得到(除非把返回值再传给一组回调)。之所以我坚持回调式异步优越先进,正是因为这一点,一个是新代码调用旧代码,一个是就代码调用新代码,两者扩展性可见一斑,不知你是否承认,还是说wind.js将要解决这一点? 以上不论async,step,xchain以及wind.js如何,只是针对回调风格的和返回值风格而言。 总之,与其花精力和代价(破坏了代码的简洁,受制于eval的外围闭包而不能简化)来得到串行的异步框架,还不如花在研究如何让回调式的异步更符合直觉的框架。尽管后者在理解起来或许比串行的好难,但是需求发生变动的时候,后者更敏捷。

@thynson 听不懂你的新代码旧代码调用关系……不过,你居然说Wind.js破坏了代码的简洁,居然说Wind.js没法简化代码……Wind.js最nb的地方就是让代码简单地一塌糊涂好不好,你到底用没用过Wind.js啊,算了我还是继续情何以堪去了……

@thynson 写个5,6千行代码的项目,你就知道用回调的痛苦了,像个金字塔, 到处都在递归,或许你能想出个神奇的办法来简化它,不过,我不行,我也认为不可能 递归啊同学,递归来个几百个就恐怖了 但是几百个for循环就不恐怖了 平铺展开型的代码,比到处都是callback,深度嵌套型的,看起来像三维立体的代码容易看得多

@thynson 来个实在的,你能用你的xchain写一个for循环我就服你,每隔一秒循环一次

@jeffz 我当然用过,每一次编译一个函数要套上eval等好几个东西,冗余东西太多了,当然这不是大问题。嘛,其实也没什么好争的。你要说我偏执于回调也没错,js这种回调如此廉价的语言不用回调太可惜了。

@151263 我之所以写xchain就是因为曾经写了个嵌套七八层的网页脚本,当时还不知道async和step。async,step以及我的xchain,目的都是把嵌套的回调平铺开来。至于每一秒一个循环,js的正解就是setTimeout或者setInterval。用js就应该Think in async way,不要纠结不能阻塞在控制流当中。

@thynson 那就是用setTimeout或者setInterval再加上递归来实现咯, 只能是到处用递归咯,或者跨越了两三个方法的间接递归,好不好,我就不知道了 反正,我觉得不好

@thynson Wind.js就是自动帮你用上回调嘛。好吧,的确有人看到流产打折就想去怀个孕啥的,嘿嘿……顺便说个事情,今天刚遇到了Step的作者Tim,当面交谈过,他现在已经不更新Step了,而在搞Lua上的Node.js,因为他认为coroutine对于异步编程实在太有用了。他也在把Node.js移植到SpiderMonkey,因为那里已经有了generator。当然你也可以说只是他堕落了,我只是说一个事实,呵呵。

@151263 递归没什么不对,递归的表达能力要比循环强很多。这不是我的观点,你可以去问问玩lisp及其方言的那些人,当然这需要思维的转变。

@jeffz lua我不评论。而generator其实是高阶函数的语法糖,并没有真正改变什么。

@jeffz 赵哥,泸JS有没有完整的视频上传啊,哈哈,我在广州,没有去看, 应该有很多人跟我一样,都想看一下视频 look 一 look,瞧一瞧

@thynson 递归和无谓递归是两码事,LISP可以表达的东西JS表达起来有硬伤,你真该把我的文章都看一遍,别把LISP当红包书护体。generator又变成高阶函数语法糖,并没有真正改变什么了,信口开河真是轻松啊……

@151263 有录像,什么时候发布就不知道了……

@jeffz 比如拿高阶函数简单的模仿python的xrange就是这样

function xrange(n) {
    var i = 0;
    return function() {
        if (i < n) return i++;
        else return undefined;
    }
}

yield不过是把隐式创建了闭包保存状态(或者其他途径),没有yield,高阶函数也能做到。你认为我这是信口开河么?说实话,你既然能写出wind.js,我觉得你应该很容易看出这一点。 至于递归,有时候在描述解决思路上,要比直接的循环要好,大部分算法以及概念难道不是以递归来描述的?扯远了。

@jeffz 录像ppt都需要

@jeffz 再咬文嚼字一下,大部分算法->许多算法。当然我也没有说循环一无是处。

@thynson 唉,算了不提了,这都能叫做一样……

@jeffz 录像ppt都需要。。

@jeffz 我这两个例子里面只是强调用回调触发下一个步骤,虽然没有用异步,但与使用异步的时候用回调使一样的。

reduce类似erlang的lists:foldl,就是给一个初始值,遍历数组每一个元素做相应的异步处理,比如异步的网页抓取、文件操作、数据库操作,每一步得到一个结果传入下一步,遍历完得到最终结果。

reduce和waterfall比较适合每个异步都需要上个步骤结果的异步操作,如果每一个步骤操作一样,只是一些参数不同,可以将参数作为数组带入reduce,比如一堆用户id,从数据库中取出一些信息做统计;如果每个步骤操作都不同,就用waterfall,比如说一个用户id,先从数据库中取出一些数据,然后根据这些数据到baidu里搜点东西,再将这些结果再写回数据库。

回到顶部