1. 设计背景
JavaScript动作迁移器来源于这样的环境:某个操作因为代码复用和增强其内聚性而被分为几个单独的动作(函数),这些动作中在逻辑上存在先后关系,即前一个动作完成后才能继续下一个动作,但是某些动作却是异步的(asynchronous),这样在编码过程中就不能按过程调用的方式来编写,必须将异步动作的下一个动作放到该异步动作中,这种调用方式,在代码量很小,其逻辑不是很复杂的情况下,还是可以的,但是当异步动作中代码量较大且逻辑复杂的情况下,会增加函数的耦合度,降低代码的阅读性,加大代码维护的难度。
为了减少代码的耦合,是逻辑更加清晰,于是设计产生了动作迁移器。使用动作迁移器,所有的动作都将在操作开始前进行定义,操作开始后,就将按照定义的动作序列调用各个动作。并且,每个动作可按照其运行的状态调用其他不同的动作或是内部不同的状态。
2. 术语解释
首先,解释一下该动作迁移器所涉及到的术语的含义:
动作迁移器(ActionTransfer),维护的是一系列动作的执行路线,使动作能够按照指定的迁移路径运行下去。
动作(Action),指的是一个确定的完整的执行过程,而不是执行片段,如判断是否为真之类的,该动作完成后没有后续的与该操作处理相关的过程,但其可以有执行后得到的数据,该数据可以被后续动作继续使用。
迁移路径(TransferPath),即一个动作到下一个动作的运行线路,它规定了某个动作执行完后,其后续所要执行的动作,或是该动作的完结。每个动作都可以有执行状态,可以从动作的某个状态迁移到另一个动作,或另一个动作的某个状态,也可以是某个动作的不同状态间的转移。
动作状态(简称状态,Status),即某个动作运行完毕后,可以通过状态指示其执行是成功还是失败或是出现异常等,动作迁移器可以根据其执行状态,沿着迁移路径继续进行处理。
动作的迁移可以是没有起点的,也可以有多个起点,动作迁移器不需从起点开始迁移,可以从任何一个动作开始迁移,开始迁移后,其迁移的路径是按照指定的路径运行下去,直到该条路径的终点(即没有下一个迁移动作)。
3. 功能结构
该动作迁移器内置的属性有当前动作(action)、初始动作(initAction)、和迁移路径(transferPath),并设方法start(启动初始动作)、setAction(设置当前迁移动作)、transfer(根据动作状态进行指定的迁移操作)、clear(清除迁移路径)。详细设计代码如下:
ActionTransfer = Ext.extend(Object, {
action : 'start'
, constructor : function(config, scope) {
var conf = config || {};
this.scope = scope || this;
this.initAction = conf.initAction || 'start';
// 迁移路径,包含start和end动作
this.transferPath = Ext.applyIf(conf.path || {}, {
'start' : function() {},
'end' : function() {}
});
if (conf.auto === true) {
this.start();
}
}
/**
* 启动迁移
* @return {ActionTransfer} this
*/
, start : function() {
return this.setAction(this.initAction).transfer();
}
/**
* 设置当前动作
* @param {String} action the current action's name
* @param {Function} fn the function will be called, after setting action
* @return {ActionTransfer} this
*/
, setAction : function(action, fn) {
if (!this.transferPath) {
alert('ActionTransfer : no defined route chain');
} else if (action && action != ''
&& this.transferPath[action]) {
this.action = action;
if (typeof fn == 'function') {
fn.apply(this.scope);
}
} else {
alert('ActionTransfer : the specified action('
+ action + ') isn\'t defined');
}
return this;
}
/**
* 根据当前动作的运行状态进行迁移(调用状态指定的方法)
* @param {String} status the name of action's status
* @param {Arguments...} args... the arguments which are used by the transfer function
* @return {ActionTransfer} this
*/
, transfer : function(status/*, args...*/) {
var args = Array.prototype.slice.call(arguments, 1),
name = this.action,
action = this.transferPath[name];
if (!this.transferPath) {
alert('ActionTransfer : no defined transfer path');
} else if (!name) {
alert('ActionTransfer : no action');
} else if (typeof action == 'function') {
action.apply(this.scope, args);
} else if (typeof action == 'object'){
if (!action[status]) {
status && status != ''
? alert('ActionTransfer : undefined status('
+ status + ') of action('
+ name + ')')
: alert('ActionTransfer : unknown status');
} else if (typeof action[status] == 'function') {
action[status].apply(this.scope, args);
} else {
alert('ActionTransfer : '
+ 'no executable transfer function');
}
} else {
alert('ActionTransfer : Oh, I cann\'t do with action('
+ name + ')');
}
return this;
}
/**
* 清除迁移路径
* @return {ActionTransfer} this
*/
, clear : function() {
delete this.transferPath;
return this;
}
});
迁移路径(transferPath)是关联数组(按字符串索引元素,而非数字索引的数组)对象,其中为动作对应的执行函数或动作对应的状态集合,在状态集合中为状态对应的执行函数。如:
transferPath = {
'start' : function() {
// do something for starting
},
'request' : {
'success' : function() {
// do something...
},
'failed' : function() {
// do other thing...
}
}
};
方法setAction中的参数fn是在设置好当前动作后所执行的函数,该函数可以用于启动所设置的当前动作,或是做一些准备工作。
要进行迁移,可以有如下两种方式:
修改当前动作后立即进行迁移:
transfer.setAction('动作名称').transfer('所设定的动作的状态', '该动作所传递的数据');
不修改当前动作并进行迁移:
transfer.transfer('当前动作执行的状态', '当前动作所传递的数据');
4. 适用范围
动作迁移器(ActionTransfer)适用于某个过程可以被细分为几个单独的动作(Action),Action之间需要一定的数据传递,但是传递数据的Action是异步的,在异步的操作中又需要针对不同的运行状态调用并将数据传递到其他不同的Action的情况。特别是,当异步Action可以被多个不同的过程所使用,但该Action完成后调用的Action又不相同时,动作迁移器便可派上用场,它可以极大地简化代码,减少代码间的耦合和代码的重复编写,并且使过程更加清晰、明了,利于代码的理解和维护。
5. 案例分析
为了加深对该动作迁移器的认识,现举例如下。
假如,有一个B/S结构的程序需要添加个人私章功能,而且用户的私章是保存在服务器端的,在私章的加盖操作时的过程是这样的:
用户点击“加盖私章”按钮,客户端检查是否已经获取有私章信息,如果有私章信息,则进入加盖操作,如果没有,则将向服务器请求私章信息。
请求私章数据的过程,使用的是Ajax技术,其为异步动作。在该动作中,如果成功获取私章,则将进入加盖操作,如果获取失败或是服务器连接异常,则通常需要弹出错误或异常提示。
在请求过程中,如果服务器返回的私章数据为空,表明用户还未上传私章,则其将不能使用私章,所以,这时需要弹出上传对话框,待用户上传完成后,再进入加盖动作。
上传对话框使用的是Ext,其创建也是异步的,并且上传动作也是使用的Ajax,在成功上传后,将调用加盖动作,失败或异常时也需弹出提示。
整个过程大概就是这样,如果按照一般的方法,我会这样写这个加盖过程的代码,如下:
startStamp : function() {
if (!this.priData) {
// 无私章信息,则发送请求
this.request();
} else {
// 否则,直接加盖私章
this.stamp(this.priData);
}
}
, request : function() {
var me = this;
Ext.Ajax.request({
success : function (response) {
if (success) {
var sig = null;
if (!data) {
// 私章不存在,上传
me.popupWin();
});
} else {
// 已上传,则直接加盖
me.stamp(data);
}
} else {
Ext.Msg.alert('失败', '错误信息');
}
}
});
}
上传窗口的创建过程在此就不列出了,下面仅列出上传的Ajax请求过程:
if (form.form.isValid()) {
form.form.submit({
success : function(form, action) {
// 加盖私章
me.stamp(data);
win.close();
},
failure : function(form, action) {
if (action.result) {
Ext.Msg.alert('失败', '私章上传失败');
} else {
Ext.Msg.alert('失败', '私章上传失败,具体原因无法得知,请联系服务器管理员获取详情');
}
win.close();
}
});
}
从以上过程可以看出,在异步动作中都显式地调用了其下一步所涉及到的动作,该方法的弊端在于,如果后来的需求有变导致下一步动作也改变了,就需要重新修改调用的动作,这便增加了代码的耦合性,容易造成“牵一发而动全身”的副作用。
但是,在引入动作迁移器后,情况将得到很大程度的改善。
首先,看一下私章加盖的动作迁移图:
接着,看一下具体的代码:
createTransferfer : function() {
var me = this, win = null,
transfer = new ActionTransfer({
initAction : 'start',
auto : false,
path : { // 私章加盖(stamp) -- S
'start' : function() { // S-1: 开始
// S-1-1: 已获取到私章数据,迁移到加盖(stamp)动作S-3
if (me.priData) {
transfer.setAction('stamp')
.transfer(null, me.priData);
}
// S-1-2: 未获取到私章数据,则请求私章数据,
// 将迁移到请求(request)动作S-2
else {
me.request();
}
}
, 'request' : { // S-2: 请求私章数据
'success' : function(data) { // S-2-1: 获取成功
transfer.transfer('finish'); // 内部迁移: 请求过程结束
// S-2-1-1: 有私章数据,则迁移到加盖(stamp)动作S-3
if (data) {
transfer.setAction('stamp')
.transfer(null, data);
}
// S-2-1-2: 无私章数据,弹出上传框,
// 在点击上传按钮后迁移到上传(upload)动作S-4
else {
win = me.popupWin();
}
}
, 'failed' : function(msg) { // S-2-2: 获取失败
transfer.transfer('finish');
// S-2-2-1: 弹出失败提示
Ext.Msg.alert('失败', msg);
}
, 'abort' : function(msg) { // S-2-3: 获取异常
transfer.transfer('finish');
// S-2-3-1: 弹出异常提示
Ext.Msg.alert('异常', msg);
}
, 'finish' : function() { // S-2-4: 请求完成
// S-2-4-1: 取消表单mask
me.form.unmask();
}
}
, 'stamp' : function(sigData) { // S-3: 加盖私章
me.stamp(sigData);
}
, 'upload' : { // S-4: 上传私章
'success' : function(data) { // S-4-1: 上传成功
// S-4-1-1: 迁移到加盖(stamp)动作S-3
transfer.transfer('finish');
transfer.setAction('stamp')
.transfer(null, data);
}
, 'failed' : function(msg) { // S-4-2: 上传失败
transfer.transfer('finish');
// S-4-2-1: 弹出失败提示
Ext.Msg.alert('失败', msg);
}
, 'abort' : function(msg) { // S-4-3: 上传异常
transfer.transfer('finish');
// S-4-3-1 : 弹出异常提示
Ext.Msg.alert('异常', msg);
}
, 'finish' : function() { // S-4-4: 上传结束
// S-4-4-1: 关闭上传窗口
win.close();
}
}
}
});
return transfer;
}
私章请求过程修改如下:
request : function() {
var me = this;
me.transfer.setAction('request');
Ext.Ajax.request({
success : function(response) {
if (success) {
me.transfer.transfer('success', data);
} else {
me.transfer.transfer('failed', '错误信息');
}
}
, failure : function(response) {
me.transfer.transfer('abort', '出现异常');
}
});
}
上传请求过程修改如下:
if (form.form.isValid()) {
me.transfer.setAction('upload');
form.form.submit({
success : function(form, action) {
me.transfer.transfer('success', data);
},
failure : function(form, action) {
var msg = '私章上传失败';
var status = 'failed';
if (!action.result) {
status = 'abort';
}
me.transfer.transfer(status, msg);
}
});
}
最后,按如下过程初始化私章加盖的迁移器,并启动即可:
startStamp : function() {
var me = this;
me.transfer = me.createTransferfer();
me.transfer.start();
}
怎么样,是不是简单多了?
6. 后记
虽然,该动作迁移器目前针对的是JavaScript编程,但是进行扩展修改后应该也可以用到其他编程语言中,不管怎么说,我觉得这种思路还是比较好的,当然,如果有更好的方法,也希望大家能提出来,大家共同讨论一下。
- 大小: 15.2 KB
分享到:
相关推荐
Laravel Livewire组件属性和动作布线 方便的指令,用于if,each,include等语句 用于安装,身份验证,组件,CRUD,模型和自动迁移的命令 自动组件布线 自动模型迁移 自动用户时区 简易的应用程序版本控制 PWA功能...
没有样板Redux 永远不要再写化简,动作或担心不变性了! 与香草精油一起存放 存放香草还原剂和基本状态 与香草还原剂和React路由器一起存放 ... 请参阅迁移部分,以获取有关如何在no-boilerplate-redux使用香
GitHub动作:Fastlane 此操作将执行通过的通道。 :information: 迁移到2.0.0。 由于此操作中的硬编码Ruby版本存在各种问题,因此我决定将设置Ruby版本的责任移交给该操作的用户。 因此,如果您以前使用的是此操作...
控制器:动作,强参数 Rails路由 Active Storage上载文件 在Rails应用程序中使用Bootstrap,Devise,Kaminari gem 技术栈 后端: Ruby2.4 Rails 5.2.x 数据库:Postgres 9.6 宝石:Devise,Kaminari,Bootstrap ...
将“追求目标”中的小节包含在相应的“程序动作”中。 程序行动清单 计算单? 步骤7之前的调解 脚本加载器? 经过[href]的文档 迁移到图标字体 大按钮 针对小尺寸进行了优化的另一种字体: 配置文件和计算机图标...
已淘汰挂钩访问redux :warning: 现在提供了钩子支持。我强烈建议您使用官方的实施。如果您已经使用此程序包...为了理解从钩子连接到调度功能的绑定动作创建器的切换,我建议您阅读。入门安装yarn add use-redux useRedu
允许您撤消所有redux动作的库,不仅限于上一次 开始吧 从原始redux迁移: - import { createStore } from 'redux'; + import createStoreCreator from 'redux-undo-any'; + const createStore = ...
从v2 ,此Hugo设置操作已迁移到JavaScript(TypeScript)操作。 我们不再构建或提取Hugo泊坞窗映像。 由于此更改,我们可以在不到几秒钟的时间内完成此操作。 (Docker基本操作花费了大约1分钟或更多的执行时间来...
Polyfill 到 nicolaslopezj:roles for alanning:roles 就地升级套件,用于从 alanning:roles 流星包...在 nicolaslopezj:roles 上,这些角色中的每一个都变成了动作,例如: { userId: qx4RqmpJsZxfxA8bj, roles:
当想要从 CircleCi 迁移到 Github 操作并且您想要逐步执行此操作时(例如,在 github 操作中运行第一个作业,然后触发第二个作业),这会很有用。 默认值适用于大多数情况。 但是org 、 repo和branch也很有用。输入...
生成控制器/动作 生成js / less / sass模板 进行中 CLI支持内联 文献资料 该文档正在进行中,但可以在此处访问,最后将提供完整的教程环境集和测试: 安装 要求 PHP的> = 7.2 使用作曲家 { "require": { "v-...
用于Auth0管理API的正在进行中的非正式Apollo graphql包装器。 出于娱乐目的而构建,主要是作为构建休息支持的阿波罗api的一种练习。从严格的api版本开始,可能会迁移到基于ui的架构 我想全面实施。如果您想提供帮助...
执行迁移动作生成对应表和初始数据 设置静态资源文件目录(不用写public) 发布资源到对应目录,设置访问路径(替代是admin) 设置静态资源CDN(默认没有,替换本地) 大功告成 可以在供应商/ comingdemon / admin-...
强调: 模块化RESTful API ES6类基于动作基于SQL(PostgreSQL与objection.js) 迁移(knex.js) 验证(JWT /访问令牌/刷新令牌) Cookie支持基于角色的访问控制要求验证CRUD(用户,发布资源) 自动化的API文档实施...
尝试使用单个ChessBoard,并将我所做的动作保留在堆栈中搜索树结束后撤消移动。 从分食系统更改为评估板系统。 基本功能: 本地比赛 与服务器端计算机 用户登录 保存用户匹配 未来待办事项: Google Cloud ...
xe-crawler 希望让使用者专注于领域逻辑而不用考虑调度、监控等问题,并且稍加改造就能用于系统监控、ETL 数据迁移等领域。更多的 xe-crawler 设计思想、设计规范参考。Usage & DevelopmentStandalone Crawler ...
2.5.3 迁移旧版Visual Studio创建的网站 49 2.6 Visual Studio调试 51 2.6.1 单步调试 52 2.6.2 变量监视 54 2.6.3 高级断点 55 2.7 WebDevelopment Helper 56 2.8 总结 57 第3章 Web窗体 58 3.1 ...
目录直接编辑文件夹式项目其他plone.restapi方法分期运行自定义JS代码添加和编辑块后运行JS 显示叠加层后运行JS 更多JS事件修改Simplelayout配置OpenGraph支持从ftw.contentpage迁移客户资料库建造测验入门工具箱...
2.5.3 迁移旧版Visual Studio创建的网站 2.6 Visual Studio调试 2.6.1 单步调试 2.6.2 变量监视 2.6.3 高级断点 2.7 WebDevelopment Helper 2.8 总结 第3章 Web窗体 3.1 页面处理 3.1.1 HTML表单...