Reflux系列01:异步操作经验小结
写在前面
在实际项目中,应用往往充斥着大量的异步操作,如ajax请求,定时器等。一旦应用涉及异步操作,代码便会变得复杂起来。在flux体系中,让人困惑的往往有几点:
- 异步操作应该在actions还是store中进行?
- 异步操作的多个状态,如pending(处理中)、completed(成功)、failed(失败),该如何拆解维护?
- 请求参数校验:应该在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
标识:
- 操作是异步的。
- 异步操作是分状态(生命周期)的,默认的有
completed
、failed
。可以通过children
参数自定义请求状态。 - 在store里通过类似
onAddTodo
、onAddTodoCompleted
、onAddTodoFailed
对请求的不同的状态进行处理。
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:异步操作经验小结的更多相关文章
- java io系列01之 "目录"
java io 系列目录如下: 01. java io系列01之 "目录" 02. java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括 ...
- SAP接口编程 之 JCo3.0系列(01):JCoDestination
SAP接口编程 之 JCo3.0系列(01):JCoDestination 字数2101 阅读103 评论0 喜欢0 JCo3.0是Java语言与ABAP语言双向通讯的中间件.与之前1.0/2.0相比 ...
- Java 集合系列 01 总体框架
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java 之 I/O 系列 01 ——基础
Java 之 I/O 系列 目录 Java 之 I/O 系列 01 ——基础 Java 之 I/O 系列 02 ——序列化(一) Java 之 I/O 系列 02 ——序列化(二) 整理<疯狂j ...
- JavaScript进阶系列01,函数的声明,函数参数,函数闭包
本篇主要体验JavaScript函数的声明.函数参数以及函数闭包. □ 函数的声明 ※ 声明全局函数 通常这样声明函数: function doSth() { alert("可以在任何时候调 ...
- 委托、Lambda表达式、事件系列01,委托是什么,委托的基本用法,委托的Method和Target属性
委托是一个类. namespace ConsoleApplication1 { internal delegate void MyDelegate(int val); class Program { ...
- [.NET MVC4 入门系列01]Helloworld MVC 4 第一个MVC4程序
[.NET MVC4 入门系列01]Helloworld MVC 4 第一个MVC4程序 一.练习项目: http://www.asp.net/mvc/tutorials/mvc-4/gettin ...
- php从入门到放弃系列-01.php环境的搭建
php从入门到放弃系列-01.php环境的搭建 一.为什么要学习php 1.php语言适用于中小型网站的快速开发: 2.并且有非常成熟的开源框架,例如yii,thinkphp等: 3.几乎全部的CMS ...
- C#程序集系列01,用记事本编写C#,IL代码,用DOS命令编译程序集,运行程序
本篇主要体验:编写C#,IL代码,用"VS2012开发人员命令提示"编译成程序集,并运行程序. □ C#文件编译为程序集 →在F盘创建as文件夹→在as文件夹下创建MyClass. ...
随机推荐
- 关于npm run build打包后css样式中的图片失效的问题(如background)
平时run dev都能正常显示的css背景图片在npm run build打包后竟然不显示了(写在标签对中的图片都可以正常显示),而且dist/static/img目录下是确实有这张图片的,于是查看打 ...
- SELECT查询结果集INSERT到数据表
简介 将查询语句查询的结果集作为数据插入到数据表中. 一.通过INSERT SELECT语句形式向表中添加数据 例如,创建一张新表AddressList来存储班级学生的通讯录信息,然后这些信息恰好存在 ...
- Linux 补丁生成与使用
我们在升级Linux 内核的时候,难免会接触到补丁的知识.下面对如何生成补丁和如何打补丁作讲解. 生成补丁: 制作 hello.c 和 hello_new.c 两个文件如如下所示. ➜ diff ls ...
- Windows下文件检索的基本姿势
要点 使用FindFirstFile和FindNextFile两个WindowsAPI,并配合链表或队列存储文件夹序列. C++源码(链表存储) #include <iostream> # ...
- iOS资源大全中文版
我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列的资源整理.awesome-ios 就是 vsouza 发起维护的 iOS 资源列表,内容包括:框架.组件.测试.App ...
- wx.aui.AuiManager部分/布局翻译
wx.aui.AuiManager wx.aui.AuiManager 是AUI框架类中的主要类 wx.aui.AuiManager管理于指定的wx.Frame相关联的窗口,通过使用窗口的wx.aui ...
- 细数垃圾邮箱客户端 Live Mail 的BUG
以前用XP系统,里面自带的有outlook,使用中还行,不过bug也不少,常见的如 1.查找,邮件多了后,常常查找不到: 2.有时收件箱什么的突然空白,或部分邮件不见了(2G大小限制,超过了就不能做移 ...
- JQuery 为radio赋值问题
今天用jquery 为radio赋值,从百度查了一下方法: $("input[name='radioName'][value=2]").attr("checked&quo ...
- Maven 安装源码和文档到本地仓库
一: 1: mvn source:jar 生成源码的jar包 2: mvn source:jar install 将源码安装到本地仓库 ,可以直接mvn source:jar install 一部 ...
- 2.3.2 EditText(输入框)详解
本节引言: 上一节中我们学习了第一个 UI控件TextView(文本框),文中给出了很多实际开发中可能遇到的一些需求 的解决方法,应该会为你的开发带来便利,在本节中,我们来学习第二个很常用的控件Edi ...