Skip to content

使用step flow简化按顺序执行的业务流程

张代应 edited this page Jun 8, 2017 · 6 revisions

step-flow简介

step-flow是一个简单的业务流程控制库,借助她能够使用近似express中间件的语法方便地管理按步骤执行的业务流程。提供了流程控制、步骤跳转以及统一的错误处理。

step-flow是怎么诞生的

在写项目的过程中,经常会遇到一些业务流程是按步骤的,比如要实现一个简单的hosts代理,当代理服务器接收到一个请求之后,需要经过以下步骤:

  • 获取请求的hostname
  • 读取hosts文件配置信息
  • 解析最终的IP构造新的请求信息
  • 发送请求
  • 处理响应数据
  • 发送到客户端

上面的流程中,上一步的处理结果,在下一步中需要用到,而且每一步都需要用到一些公共的数据,也就是需要一个上下文。

当然,这只是一个简单的例子,类似这样的流程还有很多。

当前比较流行的、能够方便管理流程的库有:stepasync。这些库,各种自己的优缺点,这里不去一一点评。

如果我们对express的中间件比较熟悉,不难发现,其实中间件就是一个比较好的流程控制模式。每个流程的处理函数,都会接收到三个参数(req, res, next)reqres贯穿于整个应用的请求-响应周期,而next()则控制了中间件的执行。而这正是一个比较理想的流程控制模式,所以参考这种模式,step-flow诞生了。

step-flow的特点

step-flow的使用方法跟express中间件非常类似:

  • 使用use([step,]fn1[, fn2, ..., fnN])添加流程处理函数
  • 流程处理函数按添加的顺序执行
  • 在流程处理函数中,调用next([error, data])执行下一个流程处理函数。如果传递error,则不再执行后续的函数,转而执行错误处理函数。
  • 流程处理函数会接收到一个贯穿于整个流程的上下文对象

流程处理函数可以做的事情:

  • 执行任何逻辑代码
  • 操作上下文对象
  • 调用下一个流程处理函数
  • 同步或者异步

这些基本上与express中间件一致,除此之外,step-flow还添加了一些新的特性:

  • 在流程处理函数中,可以调用nextTo(step[, data])跳转到任意一个存在的步骤
  • 调用catch(fn[, fn1, ..., fnN])添加错误处理函数
  • 调用run([context, step])来执行流程处理函数

step-flow的API很简单,StepFlow实例就三个方法:use()/catch()/run(),在流程处理函数内部能使用的方法:next()/nextTo()

虽然很简单,但是应该已经能满足绝大部分按步骤执行的业务流程控制了。下面介绍以下具体的使用方法。

安装

step-flow已经发布到npm,可以直接使用npm安装:

npm install step-flow --save

也可以使用yarn:

yarn add step-flow

使用例子

假设我们现在有这样的一个需求:

  • 读取本地的一个.js文件
  • 给文件末尾添加Source Map信息
  • 获取文件内容的MD5值
  • 将修改后的内容写入到磁盘,文件名中加入MD5值
  • 如果任何一步出错,终止流程并打印错误信息

要实现这个需求,我们需要先创建一个StepFlow的实例,并然后添加各个步骤的处理函数。

const StepFlow = require('step-flow');
const flow = new StepFlow();

flow
  .use('read-file', readFile)
  .use('add-source-map', addSourceMap)
  .use('get-md5', getMD5)
  .use('write-file', writeFile)
  .catch(onError);

然后,要开始执行流程,调用run([context, step]),调用时可以传入context上下文参数,每一个流程处理函数,可以修改这个上下文对象。

flow.run({});

接下来,开始编写每一个步骤对应的处理函数。这些函数在执行时,都会接收到参数(ctx, next, nextTo, data)

  • ctx <Any>: 上下文对象,调用run([context, step])时传入的第一个参数,可以操作这个对象存储、修改数据。
  • next <Function>: 调用下一个方法。
  • nextTo <Function>: 跳转到其他的步骤。
  • data <Any>: 任意数据。

一般,我们可以把处理的数据放到上下文对象上,也可以把处理过后的参数通过data传递到下一个函数。

先看看文件读取的处理函数readFile,这个功能很简单,使用fs.readFile()读取文件内容,然后把文件内容放到上下文,再调用next()

function readFile (ctx, next) {
  let {filePath} = ctx;

  fs.readFile(filePath, 'utf-8', function (err, data) {
    if (err) {
      next(err);
    } else {
      ctx.fileContent = data;
      next();
    }
  });
}

注意:这里也可以直接使用next()把文件内容传递到下一个处理函数,代码如下。

function readFile (ctx, next) {
  let {filePath} = ctx;
  fs.readFile(filePath, 'utf-8', next);
}

第一步完成之后,接下来处理文件内容--添加Source Map信息,这个也比较简单,直接在文件内容后面添加字符串。

function addSourceMap (ctx, next, nextTo, data) {
  ctx.fileContent += '\n// # sourceMappingURL=/test.js.map\n';
  next();
}

剩余的步骤,模式都一样,就不再一一列举。完整的代码:

https://gist.github.com/zdying/82dbf55d2fde9ec6dfdb877991859a46

最后

如果你有什么好的建议,欢迎一起交流。也欢迎star

项目地址:https://github.com/zdying/step-flow