工作流程拆解

image.png

基础原则

  1. 每一帧为一个函数
  2. 函数可以返回纯数据,也可以返回另一帧
  3. 函数按照加入工作流的顺序向下执行

示例

  1. import {Flow} from './lib/flow.gs';
  2. var flow = Flow();
  3. function frame(data, flow){
  4. var nextData = {};
  5. return nextData;
  6. }
  7. // 主流程
  8. flow.push(frame)
  9. flow.push(frame)
  10. flow.push("abc",frame)
  11. flow.push(frame)
  12. flow.push(frame)
  13. //分支流程2
  14. flow.push(frame)
  15. flow.push(frame)
  16. flow.push(frame)
  17. flow.push(frame)
  18. flow.push(frame)
  19. //分支流程1
  20. flow.push(frame)
  21. flow.push(frame)
  22. flow.push(frame)
  23. flow.push(frame)
  24. //其他状态
  25. flow.push(frame)
  26. //开始流程
  27. flow.start();

Flow.gs

请直接拷贝以下代码到 src/lib/flow.gs 文件中去

  1. function frameDescribe(stepIndex, data) {
  2. return {
  3. IS_FRAME: true,
  4. stepIndex,
  5. data: data || {}
  6. }
  7. }
  8. function copy(data) {
  9. return typeof data === 'object' ? JSON.parse(JSON.stringify(data)) : data;
  10. }
  11. export function Flow() {
  12. // 历史栈 index 记录
  13. var stepIndexRecord = [];
  14. // 历史栈 关键帧 记录
  15. var stepFrameRecord = [];
  16. var stepDataRecord = [];
  17. var flowList = [];
  18. var eventFlow = {};
  19. var stepIndex = 0;
  20. var context = {}
  21. function isBack(input) {
  22. return String(input) === "back";
  23. }
  24. function push(keyFrame, fn) {
  25. if (typeof keyFrame === "string") {
  26. flowList.push([fn, keyFrame]);
  27. } else {
  28. flowList.push([keyFrame]);
  29. }
  30. }
  31. function start(data) {
  32. stepIndex = 0;
  33. stepIndexRecord = [];
  34. stepFrameRecord = [];
  35. stepDataRecord = []
  36. next(data !== undefined ? data : {});
  37. }
  38. function next(data) {
  39. // 超出所有步骤
  40. if (stepIndex >= flowList.length) {
  41. stepIndex = 0;
  42. }
  43. var frame = flowList[stepIndex];
  44. if (frame) {
  45. var framefn = frame[0];
  46. // 执行记录
  47. stepIndexRecord.push(stepIndex);
  48. stepFrameRecord.push(frame[1]);
  49. // 数据记录
  50. stepDataRecord.push(copy(data));
  51. // 在当前 flow 中保存 data
  52. context.data = data;
  53. var ret = framefn(data, context);
  54. if (ret && ret.IS_FRAME) {
  55. // 当前 flow 的其他状态
  56. stepIndex = ret.stepIndex;
  57. ret = ret.data;
  58. } else {
  59. stepIndex++;
  60. }
  61. // 正常执行下一步
  62. next(ret);
  63. } else {
  64. throw new Error("not frame index: " + stepIndex);
  65. }
  66. }
  67. function getKeyFrame(frame, data) {
  68. if (frame === undefined) {
  69. throw new Error("frame must be string");
  70. }
  71. if (frame === "FLOW_FIRST") {
  72. return frameDescribe(0, data)
  73. }
  74. if (frame === "FLOW_LAST") {
  75. return frameDescribe(flowList.length - 1, data)
  76. }
  77. for (var i = 0; i < flowList.length; i++) {
  78. if (flowList[i][1] === frame) {
  79. return frameDescribe(i, data)
  80. }
  81. }
  82. throw new Error("not find " + frame + " key");
  83. }
  84. function has(frame) {
  85. try {
  86. getKeyFrame(frame);
  87. return true;
  88. } catch (e) {
  89. return false;
  90. }
  91. }
  92. function getFirstFrame(frame) {
  93. return getKeyFrame("FLOW_FIRST");
  94. }
  95. function getLastFrame(frame) {
  96. return getKeyFrame("FLOW_LAST");
  97. }
  98. function getCurrentFrame(data) {
  99. return frameDescribe(stepIndex, data);
  100. }
  101. // 从新播放当前帧,不会被记录到历史
  102. function replayCurrentFrame(data) {
  103. var nextFrameDescribe = getCurrentFrame(data);
  104. stepIndexRecord = stepIndexRecord.slice(0, -1);
  105. stepFrameRecord = stepFrameRecord.slice(0, -1);
  106. stepDataRecord = stepDataRecord.slice(0, -1);
  107. return nextFrameDescribe;
  108. }
  109. // 删除当前帧,使历史记录中不包含当前帧
  110. function deleteCurrentFrame(){
  111. stepIndexRecord = stepIndexRecord.slice(0, -1);
  112. stepFrameRecord = stepFrameRecord.slice(0, -1);
  113. stepDataRecord = stepDataRecord.slice(0, -1);
  114. }
  115. // 返回上一个来源帧
  116. function back() {
  117. var index = 0;
  118. var data = {}
  119. if (stepIndexRecord.length >= 2) {
  120. // 删除最后一个元素
  121. index = stepIndexRecord[stepIndexRecord.length - 2];
  122. data = stepDataRecord[stepDataRecord.length - 2];
  123. stepIndexRecord = stepIndexRecord.slice(0, -2);
  124. stepFrameRecord = stepFrameRecord.slice(0, -2);
  125. stepDataRecord = stepDataRecord.slice(0, -2);
  126. } else {
  127. stepIndexRecord = [];
  128. stepDataRecord = [];
  129. }
  130. return frameDescribe(index, data);
  131. }
  132. // 返回历史栈里固定的一个关键帧
  133. function backTo(frame, data){
  134. if(frame === undefined){
  135. throw new Error('back to must be frame key')
  136. }
  137. const index = stepFrameRecord.indexOf(frame);
  138. if(index > -1){
  139. var historyData = data || stepDataRecord[index];
  140. stepIndexRecord = stepIndexRecord.slice(0, index);
  141. stepFrameRecord = stepFrameRecord.slice(0, index);
  142. stepDataRecord = stepDataRecord.slice(0, index);
  143. return getKeyFrame(frame, historyData);
  144. };
  145. throw new Error('back to not found :'+ frame)
  146. }
  147. // 从固定帧开始执行
  148. function startFrom(frameDescribe, data) {
  149. if (typeof frameDescribe === "string") {
  150. if (has(frameDescribe)) {
  151. frameDescribe = getKeyFrame(frameDescribe, data);
  152. } else {
  153. throw new Error(frameDescribe + " not found")
  154. }
  155. }
  156. stepIndexRecord = [];
  157. stepFrameRecord = [];
  158. stepDataRecord = [];
  159. stepIndex = frameDescribe.stepIndex;
  160. next(frameDescribe.data);
  161. }
  162. context.push = push;
  163. context.isBack = isBack;
  164. context.start = start;
  165. context.get = getKeyFrame;
  166. context.has = has;
  167. context.back = back;
  168. context.backTo = backTo;
  169. context.getFirstFrame = getFirstFrame;
  170. context.getLastFrame = getLastFrame;
  171. context.getCurrentFrame = getCurrentFrame;
  172. context.replayCurrentFrame = replayCurrentFrame;
  173. context.deleteCurrentFrame = deleteCurrentFrame;
  174. context.restart = start;
  175. context.startFrom = startFrom;
  176. return context;
  177. }

用法

import { Flow } from './lib/flow.gs';


var flow = Flow();
// 状态1
flow.push(function(data, flow){

  var code = labor.scan("xxx/lemo.json",{});

  // 跳转回上一步
  if(flow.isBack(code)){
      return flow.back();
  }

  if(code === "3"){
    //跳转到 p3
      return flow.get("abc", {})
  }

  // 跳转到下一个步
  return {}
})

// 状态2
flow.push("p3",function(data, flow){

  if(true){
    // 从第一步重新开始,会清空历史栈
      return flow.restart();
  }
    return {}
})


function fenzhifn(){
  // 从xxx流程开始执行
    flow.startFrom(flow.get("xxx", {}));
}

// options 用法
flow.push(function(){
    const options = {
      "分支1":"fenzhifn"
  }
    var code = labor.scan("xxx/lemo.json",{}, options);
})

// 结束状态处理
flow.push("release", function(){
    labor.release();
})

flow.start();

API

Flow()

var flow = Flow();

flow.data

可以获取当前帧的数据。与当前帧第一个参数data完全相等(同一个对象引用)

flow.push(function p1(data, flow){
    data === flow.data // true

  return {
      a:1
  }
})

flow.push(function p2(data, flow){
    data.a // 1
  flow.data.a // 1
})

flow.start();

flow.back()

获得上一帧,并且数据也会回到上一帧的状态

return flow.back()

flow.backTo(key)

获得历史栈中一个关键帧,并返回到这一帧,数据也会同步还原

return flow.backTo("abc")

flow.get(key)

获取一个关键帧,并传递参数

return flow.get("abc", {})

flow.getFirstFrame(data)

获取第一帧

return flow.getFirstFrame({})

flow.getLastFrame(data)

获取最后一帧

return flow.getLastFrame({})

flow.getCurrentFrame(data)

获取当前帧,会增加历史记录。

return flow.getCurrentFrame({})

flow.replayCurrentFrame(data)

重放当前帧,多次重放,不会增加历史记录。通常可以替代getCurrentFrame

return flow.replayCurrentFrame({})

flow.restart(data)

获取第一个帧,并且会重置历史帧记录

return flow.restart({})

flow.isBack(key)

判断当前用户输入是否按了back键

flow.isBack("back") // true
flow.isBack("xxxxxx") // false

flow.startFrom(key, data)

从一个固定位置启动流程

flow.startFrom(flow.get("abx", {}))
// 上下等价,建议用下面的方式
flow.startFrom("abx", {})

flow.start(data)

从第一帧开始启动流程

flow.start({});

flow.push(key, fn)

像flow中添加一帧。

// 添加帧,匿名
flow.push(function(data, flow){})
// 添加个关键帧
flow.push("abc", function(data, flow){});

// 判断关键帧是否存在
flow.has("abc") // true

flow.has(key)

判断关键帧是否存在

flow.has(key)// 返回 true or false

推荐代码组织方式

// api.gs
export testapi(a, b, c){
    return xxxx.api(a, b, c)
}

// pages.gs
export scanDeskCode(a, options){
    return labor.scan("/xxx/xxx.lemo.json", {a}, options)
}


// start.gs
import {Flow} from './lib/flow.gs';
import * as API from './api.gs';
import * as page from './pages.gs';


var flow = Flow();

flow.push(function(data, flow){
    var code = page.scanDeskCode(data.a, {});
  if(flow.isBack(code)){
      return flow.back();
  }
  var res = API.testapi(code, data.a);
  return res;
})

flow.push("p2",function(){
  //...
});

flow.push("p3",function(){
  //...
});


// 流程结束
flow.push(function(){
    labor.release();
});

// 页面分支处理推荐
// function fenzhi1(){
  // 从p3开始继续执行流程
    // flow.startFrom('p3', flow.data)
// }


flow.start();