淘宝网站开发店铺什么类别,宁波seo推荐推广平台,静态wordpress,池州网站制作公司有限状态机是一种数学计算模型#xff0c;它描述了在任何给定时间只能处于一种状态的系统的行为。形式上#xff0c;有限状态机有五个部分#xff1a;
初始状态值 (initial state)有限的一组状态 (states)有限的一组事件 (events)由事件驱动的一组状态转移关系 (transition…有限状态机是一种数学计算模型它描述了在任何给定时间只能处于一种状态的系统的行为。形式上有限状态机有五个部分
初始状态值 (initial state)有限的一组状态 (states)有限的一组事件 (events)由事件驱动的一组状态转移关系 (transitions)有限的一组最终状态 (final states)
状态是指由状态机建模的系统中某种有限的、定性的“模式”或“状态”并不描述与该系统相关的所有可能是无限的数据。例如水可以处于以下 4 种状态中的一种冰、液体、气体或等离子体。然而水的温度可以变化所以其测量值是定量的和无限的。再比如管理TCP Socket连接时其生命周期内存在明显的有限状态转换。
可能有相当多的同学在开发中没意识到有限状态机的作用但是实际上我们几乎无时不刻在有意无意间使用了有限状态机。当您在开发过程中能有意识地系统地进行有限状态分析并应用有限状态机往往代表着您达到了较高的水平。
目前开源的有限状态机实现中比较知名的有
xstate堪称状态机航空母舰,功能太强大了也太复杂了学习成本非常高。Javascript State Machine功能较弱在实际试用过程中发现在进行异步切换时存在问题。jssm特点是引入自己的DSL语法来描述状态机使用起来比较别扭。
事实上从功能完整度上看xstate是第一选择但是其过于复杂了在功能与易用平衡方面并不理想。
因此我们开发了FlexState有限状态机力求在功能性、易用性上达到平衡。
FlexState是一款简单易用的有限状态机具有以下特性
支持基于Class构建有限状态机实例支持状态enter/leave/resume/done钩子事件状态切换完全支持异步操作支持定义异步状态动作Action支持状态切换生命周期事件订阅支持错误处理和状态切换中止基于TypeScript开发支持子状态核心代码90%单元测试覆盖率
Github 官网
快速入门
下面我们以开发基于nodejs/net.socket的TCP客户端为例来说明FlexStateMachine的使用。
作为例子我们为TCPClient设计以下几种状态
Initial初始状态构建socket实例后处于该阶段。Connecting连接中当调用Connect方法触发connect事件前。Connected已连接当触发connect事件后。Disconnecting正在断开当调用destory或end方法后end/close事件触发前。Disconnected被动断开当触发end/close事件后。AlwaysDisconnected: 主动断开状态IDLE: 自动添加的空闲状态状态机未启动时ERROR: 自动添加的错误状态,特殊的FINAL状态
TCPClient的状态图如下
第一步构建状态机
推荐直接继承FlexStateMachine来创建一个TCPClient实例该种方式更加简单易用。 import { state, FlexStateMachine } from flexstateclass TcpClient extends FlexStateMachine{// 定义状态static states { Initial : { value:0, title:已初始化, next:[Connecting,Connected,Disconnected],initial:true},Connecting : { value:1, title:正在连接..., next:[Connected,Disconnected] },Connected : { value:2, title:已连接, next:[Disconnecting,Disconnected] },Disconnecting : { value:3, title:正在断开连接..., next:[Disconnected] },Disconnected : {value:4, title:已断开连接, next:[Connecting]},AlwaysDisconnected : {value:5, title:已主动断开连接, next:[Connecting]}} constructor(options:FlexStateOptions){super(Object.assign({host:,port:9000,autoStart:true,context : null, // 状态上下文对象当执行动作或状态转换事件时的this指向autoStart : true, // 自动启动状态机timeout : 30 * 1000 // 当执行状态切换回调时的超时如enter、leave、done回调injectActionMethod: true, // 将动作方法注入到当前实例中 },options)) } state{when:[Initial,Disconnected,Error], // 代表只能当处于此三种状态时才允许调用连接方法 pending:Connecting, // 执行后进入正在连接中的状态}connect(){this._socket.connect(this.options) } state({when:[Connected], // 代表只有在已连接状态才允许执行断开方法pending:Disconnecting}) disconnect(){this._socket.destory()}// 当状态转换成功后会调用此方法ontTransition({error,from,to,done,timeConsuming}){console.log(从${previous}转换到current,耗时:${timeConsuming}ms) // 例 从Connecting转换到Connected,耗时12msconsole.log(this.current) // {name,value,....}}onData(data){....}}说明
以上我们创建了一个继承自FlexStateMachine来创建一个TCPClient实例并且定义了Initial、Connecting、Connected、Disconnecting、Disconnected、AlwaysDisconnected共六个状态以及状态之间的转换约束。同时状态机还会自动添加一个ERROR和IDLE状态。定义了connect和disconnect两个动作action在这两个方法前添加state代表了当执行这两个方法会导致状态变化。
第二步初始化TCPSocket
当实例化TCPClient实例后首先应该创建Socket实例。由于TCPClient实例继承自FlexStateMachine并且我们指定了Initial为初始化状态。 状态机会在实例化并启动后自动转换到Initial状态。因此我们可以在进入Initial状态前进行初始化操作。
class TcpClient extends FlexStateMachine{// 转换至Initial状态前会调用方法async onInitialEnter({retry,retryCount}){try{ this._socket new net.Socket()// 当连接成功时切换到Connected事件; 每一个状态均有一个大写的状态值实例成员// this.CONNECTEDthis.states.Connected.valuethis._socket.on(connect,()this.transition(this.CONNECTED)) this._socket.on(close,(){//.... 详见后续重连说明}) // 套接字因不活动而超时则触发这只是通知套接字已空闲用户必须手动关闭连接。// 通过事件触发方式来执行disconnect动作this._socket.on(timeout,()this.emit(disconnect))this._socket.on(error,()this.transition(this.ERROR))this._socket.on(data,this.onData.bind(this))}catch(e){if(retryCount3){retry(1000) // 1000ms后重试执行}else{ //throw e} }
}
当TCPClient实例化状态机处于IDLE状态(tcp实例.current.nameIDLE),然后状态机自动启动(autoStarttrue)将转换至Initial状态initial状态。
状态机转换至Initial状态前会调用onInitialEnter。我们可以在此方法中创建TCP Socket实例以及其他相关的初始化。onInitialEnter成功执行完毕后状态机的状态将转换至Initial。IDLE-Initial如果在onInitialEnter函数初始化失败或出错则应该抛出错误。错误将导致状态机将无法转换至Initial状态也就无法进行后续的所有操作了。一般在初始化失败时会进行如下操作 进行重试操作直至初始化成功即成功创建好Socket并进行相应的事件绑定。反复重试多次失败后也可能会放弃重试TCP Client将无法切换到Initial状态而是保持在IDLE状态。当条件具备时状态机需要重新运行即调用tcp.start()来启动状态机将重复上述过程。
第三步连接服务器
当TCPClient实例初始化完成后就可以开始连接服务器。我们可以在类上创建状态机动作connect,启动连接操作。
import { state, FlexStateMachine } from flexstateclass TcpClient extends FlexStateMachine{// 通过装饰器来声明这是一个状态动作 state({// 代表只能当处于此三种状态时才允许执行动作即调用连接方法when:[Initial,Disconnected,Error], // 执行后进入正在连接中的状态pending:Connecting }) async connect(){ this._socket.connect(this.options) }
}
// 创建连接实例
let tcp new TcpClient({...})
// 连接
tcp.connect()
// 状态机状态将变化: Initial - Connecting - Connected
// 如果连接出错状态将变化Initial - Connecting - Error上述的state({....})定义了一个状态机动作,代表当调用connect方法时会导致一系列的状态转换
动作名称为connect会创建一个同名的实例方法tcp.connect替换掉原始的connect方法。when参数代表了只有当前状态为[Initial、Disconnected、Error]其中一个时才允许执行connect动作。pendingConnecting代表执行connect动作前状态机的状态将暂时会切换至Connecting也就是会显示正在连接中。由于连接操作可能是耗时的所有设计一个正在连接中是比较符合实际业务逻辑的。如果执行socket.connect({...}) 出错可以通过state({retry,retryCount})来启用重试逻辑。需要注意的是 调用connect成功仅仅代表该方法在调用时没有出错并不代表已经连接成功。是否连接成功需要由socket/connect事件来触发确认。在上述中并没有显式指定当连接成功时的状态原因是因为connect方法是一个异步方法是否连接成功或失败是通过事件回调的方式转换状态的。在初始化阶段我们订阅了close、end等回调。 this._socket.on(close,()this.transition(this.DISCONNECTED))this._socket.on(end,()this.transition(this.DISCONNECTED))this._socket.on(error,()this.transition(this.ERROR))
当执行socket.connect方法后如果接收到close/end/error则会转换到对应的DISCONNECTED、ERROR状态。
至此实现了当tcp.connect方法状态转换到Connecting状态连接成功转换至Connected状态连接被断开转换至Disconnected状态出现错误时转换到ERROR状态。并且在出错时会进行一定重试操作更多关于重试的内容详见后续介绍。
第四步侦听连接状态
在TCP连接生命周期内状态机会在最后Initial/Connecting/Connected/Disconnecting/Disconnected/AlwaysDisconnected状态之间进行转换我们希望可能侦听状态机的状态转换事件以便在连接发生状态转换时进行一些操作此时就可以侦听各种连接事件。
侦听连接状态有两种方法
FlexStateMachine本身就是一个EventEmitter可以通过订阅事方式进行侦听。
// *****侦听某个状态事件*****tcp.on(Connected/enter,({from,to}){// 当准备进入连接前状态时触发此事件
}) tcp.on(Connected/leave,({from,to}){// 当准备要离开连接状态时触发此事件
}) tcp.on(Connected/done,({from,to}){// 当切换至连接状态后触发此事件
})
在类中也可以直接定义on状态名Enter、on状态名、on状态名Done、on状态名Leave类方法来侦听事件。
class TcpClient extends FlexStateMachine{onInitialEnter({from,to}){...} // 进入Initial状态前onInitial({from,to}){...} // 已切换至Initial状态onInitialDone({from,to}){...} // onInitialonInitialLeave({from,to}){...} // 离开Initial状态时onConnectingEnter({from,to}){...} // 进入Connecting状态前onConnecting({from,to}){...} // 已切换至Connecting状态onConnectingDone({from,to}){...} // onConnectingonConnectingLeave({from,to}){...} // 离开Connecting状态时onConnectedEnter({from,to}){...} // 进入Connected状态前onConnected({from,to}){...} // 已切换至Connected状态onConnectedDone({from,to}){...} // onConnectedonConnectedLeave({from,to}){...} // 离开Connected状态时//...所有状态均可以定义on状态名Enter、on状态名、on状态名Leave事件 }第五步断开重新连接
连接管理中的断开重连是非常重要的功能要处理此逻辑首先分析一下什么情况下会断开连接。
断开连接一般包括主动和被动两种情况:
服务器或网络问题等导致的连接断开
此种情况属于客户端被动断开连接一般会需要进行自动重新连接。服务器主动断开时客户端会侦听到end事件直接进入断开状态。即状态机不会切换到Disconnecting而是直接至Disconnected。
客户端主动断开连接
此种情况属性客户主动断开连接发就是客户端主动调用disconnect方法一般是不需要进行自动重连的。 主动断开时需要调用socket.end方法然后等待end事件的触发。状态机会经历从Disconnecting到Disconnected的过程。
无论是主动断开连接还是被动断开连接均会触发close事件因此需要在close事件触发时区别是主动断开还是被动断开。 为了更好地区别主动断开和被动断开我们可以增加一个状态AlwaysDisconnected来代表是客户端主动断开AlwaysDisconnected被设计为FINAL状态。 当状态机切换到Disconnected状态时调用connect动作方法来重新连接。当状态机切换到AlwaysDisconnected时则不进行重新连接。 两者差别在于如果是主动断开会经历Disconnecting状态而被动断开则不会经过此状态因此我们就可以在on(close)事件中处理将状态转换至AlwaysDisconnected或DISCONNECTED。
class TcpClient extends FlexStateMachine{class TcpClient extends FlexStateMachine{...// 转换至Initial状态前会调用方法async onInitialEnter({retry,retryCount}){// 在此需要确认该切换到Disconnected还是AlwaysDisconnected状态this._socket.on(close,(){// 主动调用disconnect方法时状态机才会切换到Disconnectingif(this.current.nameDisconnecting){ this.transition(this.ALWAYSDISCONNECTED)}else{this.transition(this.DISCONNECTED)}})}// 当切换至Disconnected状态的回调async onDisconnected({from,to}){await delay(3000)this.connect() // 重新执行Connect动作}//async onConnectClosed({from,to}){}state({when:Connected,pending:Disconnecting// 由于调用end方法是异步操作,需要等待close事件触发后才是真正的断开连接 // 因此不能在调用disconnected返回后就将状态设置为AlwaysDisconnected// 也就是说不要在此配置rejected参数// 假设执行this._socket.end没有出错则状态将保持在Disconnecting状态,直至this._socket.on(close,callback)时才进行状态转换// rejected: })async disconnect(){// 注意此操作是异步状态this._socket.end() }
}第六步连接认证子状态
当tcp连接成功后一般服务器会要求对客户连接进行认证才允许进行使用而认证操作login/logout是一个耗时的异步操作同样需要进行状态管理。当进入Connected状态后状态将在未认证、正在认证、已认证三个状态间进行转换并且在连接断开或者出错时马上退出这三个状态。因此就有必要引入子状态的概念。
引入子状态后对应的状态图更新如下 class TcpClient extends FlexStateMachine{static states { Connected : { value:2, title:已连接, next:[Disconnecting,Disconnected,Error] // 定义一个独立的状态机域scope:{states:{Unauthenticated : {value:0,title:未认证,initial:true,next:[Authenticating]},Authenticating : {value:1,title:正在认证,next:[Authenticated]}Authenticated : {value:2,title:已认证,next:[Unauthenticated]},}}}, } ......// 当状态机进入Connected后会启动其子状态机// 子状态机会转换到其初始状态Unauthenticated然后就可以在此执行登录动作async onUnauthenticatedEnter({from,to}){this.login() // }onAuthenticated({from,to}){}state({when:[Authenticating],pending:[Authenticating]})async login(){await this.send({// 认证信息})}state({when:[Authenticated] })async logout(){await this.send({// 注销信息}) }
}推荐
以下是我的一大波开源项目推荐:
全流程一健化React/Vue/Nodejs国际化方案 - VoerkaI18n无以伦比的React表单开发库 - speedform终端界面开发增强库 - Logsets简单的日志输出库 - VoerkaLogger装饰器开发 - FlexDecorators有限状态机库 - FlexState通用函数工具库 - FlexTools小巧优雅的CSS-IN-JS库 - Styledfc为JSON文件添加注释的VSCODE插件 - json_comments_extension开发交互式命令行程序库 - mixed-cli强大的字符串插值变量处理工具库 - flexvars前端link调试辅助工具 - yald异步信号 - asyncsignal