写在前面

在实际项目中,应用往往充斥着大量的异步操作,如ajax请求,定时器等。一旦应用涉及异步操作,代码便会变得复杂起来。在flux体系中,让人困惑的往往有几点:

  1. 异步操作应该在actions还是store中进行?
  2. 异步操作的多个状态,如pending(处理中)、completed(成功)、failed(失败),该如何拆解维护?
  3. 请求参数校验:应该在actions还是store中进行校验?校验的逻辑如何跟业务逻辑本身进行分离?

本文从简单的同步请求讲起,逐个对上面3个问题进行回答。一家之言并非定则,读者可自行判别。

本文适合对reflux有一定了解的读者,如尚无了解,可先行查看 官方文档 。本文所涉及的代码示例,可在 此处下载。

Sync Action:同步操作

同步操作比较简单,没什么好讲的,直接上代码可能更直观。

var Reflux = require('reflux');

var TodoActions = Reflux.createActions({
addTodo: {sync: true}
}); var state = [];
var TodoStore = Reflux.createStore({
listenables: [TodoActions],
onAddTodo: function(text){
state.push(text);
this.trigger(state);
},
getState: function(){
return state;
}
}); TodoStore.listen(function(state){
console.log('state is: ' + state);
});
TodoActions.addTodo('起床');
TodoActions.addTodo('吃早餐');
TodoActions.addTodo('上班');

看下运行结果

➜  examples git:(master) ✗ node 01-sync-actions.js
state is: 起床
state is: 起床,吃早餐
state is: 起床,吃早餐,上班

Async Action:在store中处理

下面是个简单的异步操作的例子。这里通过addToServer这个方法来模拟异步请求,并通过isSucc字段来控制请求的状态为成功还是失败

可以看到,这里对前面例子中的state进行了一定的改造,通过state.status来保存请求的状态,包括:

  • pending:请求处理中
  • completed:请求处理成功
  • failed:请求处理失败
var Reflux = require('reflux');

/**
* @param {String} options.text
* @param {Boolean} options.isSucc 是否成功
* @param {Function} options.callback 异步回调
* @param {Number} options.delay 异步延迟的时间
*/
var addToServer = function(options){
var ret = {code: 0, text: options.text, msg: '添加成功 :)'}; if(!options.isSucc){
ret = {code: -1, msg: '添加失败!'};
} setTimeout(function(){
options.callback && options.callback(ret);
}, options.delay);
}; var TodoActions = Reflux.createActions(['addTodo']); var state = {
items: [],
status: ''
}; var TodoStore = Reflux.createStore({ init: function(){
state.items.push('睡觉');
}, listenables: [TodoActions], onAddTodo: function(text, isSucc){
var that = this; state.status = 'pending';
that.trigger(state); addToServer({
text: text,
isSucc: isSucc,
delay: 500,
callback: function(ret){
if(ret.code===0){
state.status = 'success';
state.items.push(text);
}else{
state.status = 'error';
}
that.trigger(state);
}
});
},
getState: function(){
return state;
}
}); TodoStore.listen(function(state){
console.log('status is: ' + state.status + ', current todos is: ' + state.items);
}); TodoActions.addTodo('起床', true);
TodoActions.addTodo('吃早餐', false);
TodoActions.addTodo('上班', true);

看下运行结果:

➜  examples git:(master) ✗ node 02-async-actions-in-store.js
status is: pending, current todos is: 睡觉
status is: pending, current todos is: 睡觉
status is: pending, current todos is: 睡觉
status is: success, current todos is: 睡觉,起床
status is: error, current todos is: 睡觉,起床
status is: success, current todos is: 睡觉,起床,上班

Async Action:在store中处理 潜在的问题

首先,祭出官方flux架构示意图,相信大家对这张图已经很熟悉了。flux架构最大的特点就是单向数据流,它的好处在于 可预测易测试

一旦将异步逻辑引入store,单向数据流被打破,应用的行为相对变得难以预测,同时单元测试的难度也会有所增加。

ps:在大部分情况下,将异步操作放在store里,简单粗暴有效,反而可以节省不少代码,看着也直观。究竟放在actions、store里,笔者是倾向于放在actions里的,读者可自行斟酌。

毕竟,社区对这个事情也还在吵个不停。。。

Async 操作:在actions中处理

还是前面的例子,稍作改造,将异步的逻辑挪到actions里,二话不说上代码。

reflux是比较接地气的flux实现,充分考虑到了异步操作的场景。定义action时,通过asyncResult: true标识:

  1. 操作是异步的。
  2. 异步操作是分状态(生命周期)的,默认的有completedfailed。可以通过children参数自定义请求状态。
  3. 在store里通过类似onAddTodoonAddTodoCompletedonAddTodoFailed对请求的不同的状态进行处理。
var Reflux = require('reflux');

/**
* @param {String} options.text
* @param {Boolean} options.isSucc 是否成功
* @param {Function} options.callback 异步回调
* @param {Number} options.delay 异步延迟的时间
*/
var addToServer = function(options){
var ret = {code: 0, text: options.text, msg: '添加成功 :)'}; if(!options.isSucc){
ret = {code: -1, msg: '添加失败!'};
} setTimeout(function(){
options.callback && options.callback(ret);
}, options.delay);
}; var TodoActions = Reflux.createActions({
addTodo: {asyncResult: true}
}); TodoActions.addTodo.listen(function(text, isSucc){
var that = this;
addToServer({
text: text,
isSucc: isSucc,
delay: 500,
callback: function(ret){
if(ret.code===0){
that.completed(ret);
}else{
that.failed(ret);
}
}
});
}); var state = {
items: [],
status: ''
}; var TodoStore = Reflux.createStore({ init: function(){
state.items.push('睡觉');
}, listenables: [TodoActions], onAddTodo: function(text, isSucc){
var that = this; state.status = 'pending';
this.trigger(state);
}, onAddTodoCompleted: function(ret){
state.status = 'success';
state.items.push(ret.text);
this.trigger(state);
}, onAddTodoFailed: function(ret){
state.status = 'error';
this.trigger(state);
}, getState: function(){
return state;
}
}); TodoStore.listen(function(state){
console.log('status is: ' + state.status + ', current todos is: ' + state.items);
}); TodoActions.addTodo('起床', true);
TodoActions.addTodo('吃早餐', false);
TodoActions.addTodo('上班', true);

运行,看程序输出

➜  examples git:(master) ✗ node 03-async-actions-in-action.js
status is: pending, current todos is: 睡觉
status is: pending, current todos is: 睡觉
status is: pending, current todos is: 睡觉
status is: success, current todos is: 睡觉,起床
status is: error, current todos is: 睡觉,起床
status is: success, current todos is: 睡觉,起床,上班

Async Action:参数校验

前面已经示范了如何在actions里进行异步请求,接下来简单演示下异步请求的前置步骤:参数校验。

预期中的流程是:

流程1:参数校验 --> 校验通过 --> 请求处理中 --> 请求处理成功(失败)

流程2:参数校验 --> 校验不通过 --> 请求处理失败

预期之外:store.onAddTodo 触发

直接对上一小节的代码进行调整。首先判断传入的text参数是否是字符串,如果不是,直接进入错误处理。

var Reflux = require('reflux');

/**
* @param {String} options.text
* @param {Boolean} options.isSucc 是否成功
* @param {Function} options.callback 异步回调
* @param {Number} options.delay 异步延迟的时间
*/
var addToServer = function(options){
var ret = {code: 0, text: options.text, msg: '添加成功 :)'}; if(!options.isSucc){
ret = {code: -1, msg: '添加失败!'};
} setTimeout(function(){
options.callback && options.callback(ret);
}, options.delay);
}; var TodoActions = Reflux.createActions({
addTodo: {asyncResult: true}
}); TodoActions.addTodo.listen(function(text, isSucc){
var that = this; if(typeof text !== 'string'){
that.failed({ret: 999, text: text, msg: '非法参数!'});
return;
} addToServer({
text: text,
isSucc: isSucc,
delay: 500,
callback: function(ret){
if(ret.code===0){
that.completed(ret);
}else{
that.failed(ret);
}
}
});
}); var state = {
items: [],
status: ''
}; var TodoStore = Reflux.createStore({ init: function(){
state.items.push('睡觉');
}, listenables: [TodoActions], onAddTodo: function(text, isSucc){
var that = this; state.status = 'pending';
this.trigger(state);
}, onAddTodoCompleted: function(ret){
state.status = 'success';
state.items.push(ret.text);
this.trigger(state);
}, onAddTodoFailed: function(ret){
state.status = 'error';
this.trigger(state);
}, getState: function(){
return state;
}
}); TodoStore.listen(function(state){
console.log('status is: ' + state.status + ', current todos is: ' + state.items);
}); // 非法参数
TodoActions.addTodo(true, true);

运行看看效果。这里发现一个问题,尽管参数校验不通过,但store.onAddTodo 还是被触发了,于是打印出了status is: pending, current todos is: 睡觉

而按照我们的预期,store.onAddTodo是不应该触发的。

➜  examples git:(master) ✗ node 04-invalid-params.js
status is: pending, current todos is: 睡觉
status is: error, current todos is: 睡觉

shouldEmit 阻止store.onAddTodo触发

好在reflux里也考虑到了这样的场景,于是我们可以通过shouldEmit来阻止store.onAddTodo被触发。关于这个配置参数的使用,可参考文档

看修改后的代码

var Reflux = require('reflux');

/**
* @param {String} options.text
* @param {Boolean} options.isSucc 是否成功
* @param {Function} options.callback 异步回调
* @param {Number} options.delay 异步延迟的时间
*/
var addToServer = function(options){
var ret = {code: 0, text: options.text, msg: '添加成功 :)'}; if(!options.isSucc){
ret = {code: -1, msg: '添加失败!'};
} setTimeout(function(){
options.callback && options.callback(ret);
}, options.delay);
}; var TodoActions = Reflux.createActions({
addTodo: {asyncResult: true}
}); TodoActions.addTodo.shouldEmit = function(text, isSucc){
if(typeof text !== 'string'){
this.failed({ret: 999, text: text, msg: '非法参数!'});
return false;
}
return true;
}; TodoActions.addTodo.listen(function(text, isSucc){
var that = this; addToServer({
text: text,
isSucc: isSucc,
delay: 500,
callback: function(ret){
if(ret.code===0){
that.completed(ret);
}else{
that.failed(ret);
}
}
});
}); var state = {
items: [],
status: ''
}; var TodoStore = Reflux.createStore({ init: function(){
state.items.push('睡觉');
}, listenables: [TodoActions], onAddTodo: function(text, isSucc){
var that = this; state.status = 'pending';
this.trigger(state);
}, onAddTodoCompleted: function(ret){
state.status = 'success';
state.items.push(ret.text);
this.trigger(state);
}, onAddTodoFailed: function(ret){
state.status = 'error';
this.trigger(state);
}, getState: function(){
return state;
}
}); TodoStore.listen(function(state){
console.log('status is: ' + state.status + ', current todos is: ' + state.items);
}); // 非法参数
TodoActions.addTodo(true, true);
setTimeout(function(){
TodoActions.addTodo('起床', true);
}, 100)

再次运行看看效果。通过对比可以看到,当shouldEmit返回false,就达到了之前预期的效果。

➜  examples git:(master) ✗ node 05-invalid-params-shouldEmit.js
status is: error, current todos is: 睡觉
status is: pending, current todos is: 睡觉
status is: success, current todos is: 睡觉,起床

写在后面

flux的实现细节存在不少争议,而针对文中例子,reflux的设计比较灵活,同样是使用reflux,也可以有多种实现方式,具体全看判断取舍。

最后,欢迎交流。

Reflux系列01:异步操作经验小结的更多相关文章

  1. java io系列01之 "目录"

    java io 系列目录如下: 01. java io系列01之  "目录" 02. java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括 ...

  2. SAP接口编程 之 JCo3.0系列(01):JCoDestination

    SAP接口编程 之 JCo3.0系列(01):JCoDestination 字数2101 阅读103 评论0 喜欢0 JCo3.0是Java语言与ABAP语言双向通讯的中间件.与之前1.0/2.0相比 ...

  3. Java 集合系列 01 总体框架

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  4. Java 之 I/O 系列 01 ——基础

    Java 之 I/O 系列 目录 Java 之 I/O 系列 01 ——基础 Java 之 I/O 系列 02 ——序列化(一) Java 之 I/O 系列 02 ——序列化(二) 整理<疯狂j ...

  5. JavaScript进阶系列01,函数的声明,函数参数,函数闭包

    本篇主要体验JavaScript函数的声明.函数参数以及函数闭包. □ 函数的声明 ※ 声明全局函数 通常这样声明函数: function doSth() { alert("可以在任何时候调 ...

  6. 委托、Lambda表达式、事件系列01,委托是什么,委托的基本用法,委托的Method和Target属性

    委托是一个类. namespace ConsoleApplication1 { internal delegate void MyDelegate(int val); class Program { ...

  7. [.NET MVC4 入门系列01]Helloworld MVC 4 第一个MVC4程序

    [.NET MVC4 入门系列01]Helloworld MVC 4 第一个MVC4程序   一.练习项目: http://www.asp.net/mvc/tutorials/mvc-4/gettin ...

  8. php从入门到放弃系列-01.php环境的搭建

    php从入门到放弃系列-01.php环境的搭建 一.为什么要学习php 1.php语言适用于中小型网站的快速开发: 2.并且有非常成熟的开源框架,例如yii,thinkphp等: 3.几乎全部的CMS ...

  9. C#程序集系列01,用记事本编写C#,IL代码,用DOS命令编译程序集,运行程序

    本篇主要体验:编写C#,IL代码,用"VS2012开发人员命令提示"编译成程序集,并运行程序. □ C#文件编译为程序集 →在F盘创建as文件夹→在as文件夹下创建MyClass. ...

随机推荐

  1. "System.OutOfMemoryException" exception when you execute a query in SQL Server Management Studio (转自MSDN)

    Symptoms When you use Microsoft SQL Server Management Studio (SSMS) to run an SQL query that returns ...

  2. SQL Server如何附加只有mdf的数据库文件

    有时候SQL Server意外断电会导致SQL Server的ldf日志文件丢失或者损坏,这个时候你如果直接附加mdf文件到SQL Server会失败,这里提供一个方法可以还原只有mdf的数据库文件, ...

  3. Git钩子详解

    钩子 Git钩子是在Git仓库中特定事件发生时自动运行的脚本.可以定制一些钩子,这些钩子可以在特定的情况下被执行,分为Client端的钩子和Server端的钩子.Client端钩子被operation ...

  4. python os模块 遍历目录

    #os #os ->tree命令 import os #递归 #目录 ->文件,文件夹 -> 文件文件夹 dirpath = input('请输入你要遍历的目录\n') def ge ...

  5. Windows10 家庭版没有本地组策略解决方法

    windows 家庭版默认是没有本地组策略的, win+R运行gpedit.msc提示不存在 下面的代码保存一个cmdorbat 脚本文件add_gpedit.cmd, 并执行 add_gpedit. ...

  6. c语言:复合文字

    复合文字的意义,相当于是在C语言中,为数组类型定义了一种类似于8之于int,'w'之于char一样的常量类型,所以从C99之后,可以在一些地方使用复合文字来代替数组使用. 复合文字的声明 (]){,, ...

  7. Spark 分布式调试工具

    0. 说明 编写工具类,考察 Spark 分布式程序的执行地点 1. 工具类编写 [ JMX ] Java Management Extend , Java 管理扩展服务. 主要用于运维和监控. [测 ...

  8. Markdown 进阶

    目录 markdown进阶语法 内容目录 加强代码块 脚注 流程图 时序图 LaTeX公式 markdown进阶语法 内容目录 使用 [TOC] 引用目录,将 [TOC] 放至文本的首行,编辑器将自动 ...

  9. python 下 安装openCV

    安装openCV openCV是Intel 创建的计算机视觉库,用于计算机图像处理. 安装anaconda,在命令行中输入conda install cv2/opencv 报错汇总 网络连接问题 报错 ...

  10. 服务器 三 MQTT服务器手机开发

    目的: 实现手机4G网络控制单片机,需要搭建服务器,手机或者各种控制端远程控制. 本教程 1  MQTT服务器硬件模块 2 MQTT服务器电脑搭建 2.1自己搭建 2.2租阿里云服务器 2 MQTT服 ...