【原创】stepify:轻松管理Nodejs异步工作流

【原创】stepify:轻松管理Nodejs异步工作流

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


4 回复

【原创】stepify:轻松管理Node.js 异步工作流

在Node.js中,大部分操作都是异步的,因此我们经常需要处理大量的回调函数。这种深度嵌套的回调模式(俗称“回调地狱”)会让代码难以阅读和维护。为了解决这个问题,我们可以使用一些工具来更好地管理和组织异步工作流。

使用 stepify

stepify 是一个轻量级的库,可以帮助你更清晰地管理和执行异步任务。它允许你以一种更加结构化的方式来定义和执行任务。stepify 已经发布到了 npm,你可以使用以下命令进行安装:

$ npm install stepify

示例代码

假设我们有一个工作(workflow)需要完成,它包含多个任务(tasks),每个任务包含多个步骤(steps)。我们可以使用 stepify 来定义这些任务和步骤,并按顺序执行它们。

const Stepify = require('stepify');

// 创建一个新的工作流实例
var workflow = Stepify()
    .task('t1')
        .step('t1s1', function (done) {
            console.log('Task 1 Step 1');
            done(null, 'Data from t1s1'); // 将数据传递给下一个步骤
        })
        .step('t1s2', function (data, done) {
            console.log('Task 1 Step 2 with data:', data);
            done(null, 'Data from t1s2');
        })
    .task('t2')
        .step('t2s1', function (done) {
            console.log('Task 2 Step 1');
            done(null, 'Data from t2s1');
        })
        .step('t2s2', function (data, done) {
            console.log('Task 2 Step 2 with data:', data);
            done(null, 'Data from t2s2');
        })
        .error(function (err) {
            console.error('Error in Task 2:', err);
        })
    .task('t3')
        .step('t3s1', function (data, done) {
            console.log('Task 3 Step 1 with data:', data);
            done(null, 'Data from t3s1');
        })
        .step('t3s2', function (data, done) {
            console.log('Task 3 Step 2 with data:', data);
            done();
        })
    .pend()
    .error(function (err) {
        console.error('Error in Workflow:', err);
    })
    .result(function (result) {
        console.log('Workflow completed:', result);
    })
    .run();

解释

  1. 任务定义

    • .task('name') 定义一个任务。
    • .step('name', function) 定义一个步骤,步骤中的函数接收 done 回调函数,用于传递数据给下一个步骤。
  2. 错误处理

    • .error(function) 定义一个错误处理函数,用于捕获并处理任务或步骤中的错误。
    • 在任务级别定义的错误处理函数会优先于全局错误处理函数。
  3. 流程控制

    • .pend() 表示当前任务的定义结束。
    • .result(function) 定义一个函数,在所有任务完成后调用,可以获取最终结果。
  4. 并行执行

    • 通过 .run(['t1', 't2'], 't3') 可以指定某些任务并行执行,然后按顺序执行其他任务。

动态与静态参数

  • 静态参数:可以在定义步骤时传递静态参数,这些参数在任务执行前就已确定。
  • 动态参数:通过 donenext 方法传递,这些参数是在任务执行过程中动态生成的。

通过这种方式,你可以更清晰地组织和管理复杂的异步工作流,避免了回调地狱带来的困扰。希望这个介绍能帮助你更好地理解和使用 stepify


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

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

stepify 是一个用于管理 Node.js 异步工作流的库,可以帮助开发者避免嵌套的回调地狱,并以更清晰的方式组织和管理异步任务。

使用 stepify 管理异步工作流

首先,你需要安装 stepify

$ npm install stepify

示例代码

假设我们有一个工作流,包括三个任务:t1, t2, t3。每个任务有多个步骤。我们可以用 stepify 来定义这些任务及其步骤:

const Stepify = require('stepify');

// 创建一个新的工作流实例
var workflow = Stepify();

// 定义第一个任务 t1
workflow
    .task('t1')
    .step('t1s1', function (done) {
        console.log('t1s1');
        done(null, 'data from t1s1');
    })
    .step('t1s2', function (data, done) {
        console.log('t1s2 with data:', data);
        done(null, 'data from t1s2');
    });

// 定义第二个任务 t2
workflow
    .task('t2')
    .step('t2s1', function (done) {
        console.log('t2s1');
        done(null, 'data from t2s1');
    })
    .step('t2s2', function (data, done) {
        console.log('t2s2 with data:', data);
        done(null, 'data from t2s2');
    })
    .error(function (err) {
        console.error('Error in t2:', err);
    });

// 定义第三个任务 t3
workflow
    .task('t3')
    .step('t3s1', function (data1, data2, done) {
        console.log('t3s1 with data:', data1, data2);
        done(null, 'data from t3s1');
    })
    .step('t3s2', function (data, done) {
        console.log('t3s2 with data:', data);
        done(null, 'data from t3s2');
    })
    .error(function (err) {
        console.error('Error in t3:', err);
    });

// 执行任务
workflow
    .result(function (results) {
        console.log('Final result:', results);
    })
    .run(['t1', 't2'], 't3'); // t1 和 t2 并行执行,t3 依赖于 t1 和 t2 的结果

解释

  1. 创建工作流实例:使用 Stepify() 创建一个新的工作流实例。
  2. 定义任务:使用 .task('taskName') 定义一个任务。
  3. 定义步骤:在每个任务中,使用 .step('stepName', function) 定义步骤。
  4. 处理错误:在任务或步骤中添加 .error(function) 来处理错误。
  5. 定义最终结果处理器:使用 .result(function) 来处理最终结果。
  6. 执行工作流:使用 .run() 方法执行工作流。可以通过传递参数来控制任务的执行顺序和并发关系。

stepify 通过这种简单的方式帮助开发者更好地管理和组织复杂的异步逻辑。

回到顶部