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支持。