-
Notifications
You must be signed in to change notification settings - Fork 2
使用step flow简化按顺序执行的业务流程
step-flow是一个简单的业务流程控制库,借助她能够使用近似express中间件的语法方便地管理按步骤执行的业务流程。提供了流程控制、步骤跳转以及统一的错误处理。
在写项目的过程中,经常会遇到一些业务流程是按步骤的,比如要实现一个简单的hosts代理,当代理服务器接收到一个请求之后,需要经过以下步骤:
- 获取请求的hostname
- 读取hosts文件配置信息
- 解析最终的IP构造新的请求信息
- 发送请求
- 处理响应数据
- 发送到客户端
上面的流程中,上一步的处理结果,在下一步中需要用到,而且每一步都需要用到一些公共的数据,也就是需要一个上下文。
当然,这只是一个简单的例子,类似这样的流程还有很多。
当前比较流行的、能够方便管理流程的库有:step,async。这些库,各种自己的优缺点,这里不去一一点评。
如果我们对express的中间件比较熟悉,不难发现,其实中间件就是一个比较好的流程控制模式。每个流程的处理函数,都会接收到三个参数(req, res, next)
。req
和res
贯穿于整个应用的请求-响应周期,而next()
则控制了中间件的执行。而这正是一个比较理想的流程控制模式,所以参考这种模式,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