一个非常精简灵活的流程控制JS类框架

前段时间,由于业务需求,无意中在网上看到一篇原腾讯高级工程师张镇圳的文章《一个只有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()
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值