前段时间,由于业务需求,无意中在网上看到一篇原腾讯高级工程师张镇圳的文章《一个只有99行代码的JS流程框架》其中感悟颇深,而且正好满足现实中的需求。
需求如下:
- 有A、B、C、D四个接口;
- A接口最先请求,B、C接口根据A接口的返回值决定是并发或者串联请求;
- D接口依赖于C接口,但前提条件是A接口的返回值决定是否请求;
- 每一次所有流程走完后(包括异步)执行某个特定的任务;
尽管上述只有4的接口(实际只有三),用条件判断或者普通代码实现起来并不容易,而且扩展性和可阅读性都很差。但是用原作者的flowJS就能轻松而优雅的实现。由于业务需求,我对其flowJS进行了一些改写和优化。
实现要点:
1、一支流程flow里面可以有N个步骤step;
2、每个步骤可以设置其自身以及其前后步骤的信息和数据;
3、可以预先规划好流程的每一步,如this.setNext('步骤A').setNext('步骤B')……
4、可以在任何一步决定下一步做什么,如 this.setNext('步骤C');
5、在任何一步中,可以知道当前步是在做什么,上一步做了什么、下一步做什么,如this.getCurr()、this.getPrev()、this.getNext();
6、当前步做完后,能将结果告诉下一步(仅仅是下一步能获取到当前步传递的结果,也就是为了保护变量污染,每一步都只能获取到前一步的结果),如 给下一步传值this.nextData({name1:value1,name2:value2,……})、获取上一步传来的值this.stepData(name1)或this.stepData();
7、可以设置或获取整个流程的全局变量,这样所有的步骤都能共享该变量,如 设置全局变量值this.flowData({name1:value1,name2:value2,……}),获取全局变量值this.flowData(name1)或this.flowData();
8、上一步可以知道当前步的执行结果,成功 or 失败,如 在上一步中设置this.setNext('步骤B', successFun, failFun)、当前步中通过this.success(args)、this.fail(args)来告诉上一步;
9、当前步可以随时执行下一步,如this.next();
10、有些步骤能并行执行,并且要都执行完才能执行下一步,如 this.setNext('步骤A').setNext([步骤B1,步骤B2,步骤B3]).setNext('步骤C');
11、可以在任何时候知道当前代码流程运行过的轨迹,如flowJS.trace,这对于了解页面的执行过程会比较有帮助;
12、支持流程嵌套
13、可以随时结束流程,如flowJS.stop();
代码清单:
const INIT = 'init';
const PENDING = 'pending';
const FULLFILED = 'fullfiled';
const STOPED = 'stoped';
const REJECTED = 'rejected';
function isObject(tar) {
return {}.toString.call(tar) === '[object Object]';
}
function isFunction(tar) {
return {}.toString.call(tar) === '[object Function]';
}
function isArray(tar) {
return Array.isArray(tar);
}
function isString(tar) {
return typeof tar === 'string';
}
// 扩展对象属性方法
function extend() {
let target = arguments[0];
for (var i = 1, arg; i < arguments.length, arg = arguments[i]; i++) {
for (var prop in arg) {
arg.hasOwnProperty(prop) && (target[prop] = arg[prop]);
}
}
return target;
}
/**
* Step类
* @param {Array|String} name 一个或者多个步骤
* @param {Object} flow 流程对象
*/
class Step {
constructor(name, flow) {
// 如果是一个step对象则合并返回
if (isObject(name)) {
return extend(this, name);
}
this.name = name;
this.flow = flow;
this.nextStep = null;
this.stepData = {};
this.stepCallback = {};
this.nextData = {};
this.prevStepName = null;
this.nextStepName = null;
// 遍历生成步骤映射
this.stepMapping = [].concat(name).reduce((total, curr) => {
total[curr] = false;
return total;
}, {});
}
// 设置下一步
setNext(stepName, success = function () { }, fail = function () { }) {
this.nextStepName = stepName;
this.nextStep = new Step(stepName, this.flow);
this.nextStep.prevStepName = this.name;
this.nextStep.stepCallback.fail = fail;
this.nextStep.stepCallback.success = success;
return this.nextStep; // 链式调用
};
// 设置下一步(数组方式)
setNexts(stepNames) {
let step = this;
[].concat(stepNames).forEach(stepName => {
step = step.setNext(stepName);
});
};
// 获得当前步骤数据
getStepData(dataName) {
return dataName ? this.stepData[dataName] : this.stepData;
};
// 设置下一步数据
setNextData(data) {
extend(this.nextData, data);
};
// 获得/设置全局数据
flowData(data) {
let { flowData } = this.flow;
isObject(data) && extend(flowData, data);
return isString(data) ? flowData[data] : flowData;
};
// 获得下一步骤
getNext() {
return this.nextStepName;
};
// 获得上一步骤
getPrev() {
return this.prevStepName;
};
// 获得当前步骤
getCurr() {
return this.name;
};
// 失败回调
fail() {
this.stepCallback.fail.apply(this, arguments);
};
// 成功回调
success() {
this.stepCallback.success.apply(this, arguments);
};
// 走下一步
next(stepName, success, fail) {
if(this.flow.status !== PENDING) {
this.flow.instance.onStop && this.flow.instance.onStop(this)
return false;
}
if(stepName) {
this.nextStepName = stepName;
this.nextStep = new Step(stepName, this.flow);
}
if (!this.nextStepName) return false;
if (success) this.nextStep.stepCallback.success = success;
if (fail) this.nextStep.stepCallback.fail = fail;
// 如果父步骤存在,则继承,否则=本步骤
this.nextStep.parent = this.parent || this.name;
this.nextStep.stepData = this.nextData;
this.stepMapping[this.name] = true;
if (this.done()) {
let nextSteps = [].concat(this.nextStepName);
this.setFlowTrace(nextSteps);
// 遍历执行步骤函数
nextSteps.forEach(name => this.runStep(name));
}
};
// 设置步骤轨迹
setFlowTrace(data) {
let trace = [].concat(data).map(name => this.flow.stepPath + name);
this.flow.trace.push(...trace);
isObject(this.parent) && this.parent.flow.trace.push(...trace);
}
// 执行步骤函数
runStep(stepName) {
let { nextStep, nextStepName, flow } = this;
let fn = flow.steps[stepName];
if (typeof fn !== 'function') {
throw new Error("step not found: " + stepName);
}
// 如果是群组
if (isArray(nextStepName)) {
nextStep = new Step(nextStep);
nextStep.name = stepName
}
fn.call(nextStep, nextStep, function() {
nextStep.next.apply(nextStep, arguments)
});
};
// 该步骤或该步骤所有子步骤是否运行完毕
done() {
return Object.values(this.stepMapping).every(v => v);
}
}
// 主程序
class Flow {
constructor(steps, parent) {
this.parent = parent;
this.flow = {
steps: extend({
init: function () { }
}, steps),
status: PENDING, // 进行中:pending|完成:fullfiled|已停止:stoped|已失败:rejected
instance: this,
flowData: {},
stepPath: '',
trace: [INIT]
}
}
start() {
let { parent, flow } = this;
let initStep = new Step(INIT, flow);
if (parent) {
initStep.parent = parent;
flow.stepPath += (parent.name + '.');
parent.flow.trace.push(flow.stepPath + INIT);
}
flow.steps.init.call(initStep, initStep, function() {
return initStep.next.apply(initStep, arguments);
});
return this;
}
stop() {
if(this.flow.status === PENDING) this.flow.status = STOPED;
}
}
function flowJS(data, parent) {
return new Flow(data, parent).start()
}