【原创】stepify:轻松管理Node.js异步工作流
发布于 9 年前 作者 chemdemo 5273 次浏览 最后一次编辑是 6 年前

Node.js中基本都是异步编程,我们回想下为什么初学者很容易写出深度嵌套callback的代码?因为直观啊,一眼即懂。当然实际写的时候肯定不推荐callback套callback,需要一个工具来把一个任务完整的串起来。

目前已经发布到npm,可以使用npm直接安装:

$ npm install stepify

使用

假设有一个工作(work)需要完成,它分解为task1、task2、task3。。。几个任务,每个任务分为好几个步骤(step),使用stepify实现的伪代码如下:

var workflow = Stepify()
    .task('t1')
        .step('t1s1', fn)
        // t1s1的执行结果可以通过fn内部的`this.done`方法传给t1s2,下同
        .step('t1s2', fn)
        .step('s', fn)
    .task('t2')
        .step('t2s1', fn)
        .step('t2s2', fn)
        .step('s', fn)
        // 定义任务t2的异常处理函数
        .error(fn)
    .task('t3')
        .step('t3s1', fn)
        .step('t3s2', fn)
    // pend是指结束一个task的定义,接下来定义下一个task或者一些公共方法
    // task里边其实会先调用下pend以自动结束上一个task的定义
    .pend()
     // 处理work中每一个task的每一个(异步)step异常
    .error(fn)
    // 处理最终结果,result()是可选的(不需要关注输出的情况)
    .result(fn)
    .run()

这里可以看到,工作原理很简单,就是先定义好后执行

解释下,pend的作用是分割task的定义,表示这个task要具体怎么做已经定义好了。里边还有两个error()的调用,跟在t2后面error调用的表明t2的异常由传入这个error的函数来处理,t1和t3没有显示定义error,所以它们的异常将交给后面一个error定义的函数来处理,这个是不是很像js的时间冒泡?

默认情况下,work的执行是按照task定义的顺序来串行执行的,所以还可以这样简化:

var workflow = Stepify()
    .step('t1s1', fn)
    .step('t1s2', fn)
    .step('s', fn)
    .pend()
    .step('t2s1', fn)
    .step('t2s2', fn)
    .step('s', fn)
    .error(fn)
    .pend()
    .step('t3s1', fn)
    .step('t3s2', fn)
    .pend()
    .error(fn)
    .result(fn)
    .run()

细心的童靴可能已经发现,t1和t2后面的都有一个step——step('s', fn),这里其实还可以把它抽出来:

var workflow = Stepify()
    .step('t1s1', fn)
    .step('t1s2', fn)
    .step('s')
    .pend()
    .step('t2s1', fn)
    .step('t2s2', fn)
    .step('s')
    .error(fn)
    .pend()
    .step('t3s1', fn)
    .step('t3s2', fn)
    .pend()
    .s(fn)
    .error(fn)
    .result(fn)
    .run()

是不是很神奇?s并不是stepify内置的方法而是动态扩展出来的!

那接下来又有个问题,t1和t2都有执行两个step('s'),那额外的参数怎么传递呢?奥妙之处在于step函数,它后面还可以跟其他参数,表示在我们定义所有task之前就已经知道的变量(我叫它⎡静态参数⎦),还有任务执行过程中,如果上一个step的输出怎么传递给下一个step呢?答案是通过next或者done动态传入(我叫它⎡动态参数⎦),s(fn)只是定义一个函数体,通过静态参数和动态参数结合,可以得到不同的结果。

这还没完,我们都听过一句话,叫做“条条大路通罗马(All roads lead to Rome)”,解决问题的方式往往有多种。上面这个例子,假如外部条件变了,task1和task2它们的执行互不影响,task3的执行需要依赖task1和task2的结果,即task1和task2可以并行,这样子怎么实现呢?

很简单,奥妙在run方法:

run(['t1', 't2'], 't3');

把t1和t2放到数组中,它们便是并行执行!同理,可以变出很多种组合来。

至于一些人问的和async的区别,一两句话解释不清楚,设计理念不同,二者并不冲突,async在并发控制上面很优秀,而stepify则重在流程控制,里面也有简单的parallel支持。

2 回复

貌似Async,Q这样的模块可以解决吧。请问贵模块优势在哪?

之前的例子给的不太合适,不能直观的说明用法,重新整理了下~ async我也一直在用,最近把一个项目里之前用async实现的逻辑用stepify重写了一下,代码量没少多少,但是可读性好了不少,随便找个人都能清楚那个任务具体怎么实施。 当然,当初刚开始写node,是有点乱

回到顶部