在实际项目中,应用每每充斥着大量的异步操做,如ajax请求,定时器等。一旦应用涉及异步操做,代码便会变得复杂起来。在flux体系中,让人困惑的每每有几点:node
异步操做应该在actions仍是store中进行?git
异步操做的多个状态,如pending(处理中)、completed(成功)、failed(失败),该如何拆解维护?github
请求参数校验:应该在actions仍是store中进行校验?校验的逻辑如何跟业务逻辑自己进行分离?ajax
本文从简单的同步请求讲起,逐个对上面3个问题进行回答。一家之言并不是定则,读者可自行判别。shell
本文适合对reflux有必定了解的读者,如尚无了解,可先行查看 官方文档 。本文所涉及的代码示例,可在 此处下载。架构
同步操做比较简单,没什么好讲的,直接上代码可能更直观。异步
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('上班');
看下运行结果async
➜ examples git:(master) ✗ node 01-sync-actions.js state is: 起床 state is: 起床,吃早餐 state is: 起床,吃早餐,上班
下面是个简单的异步操做的例子。这里经过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: 睡觉,起床,上班
首先,祭出官方flux架构示意图,相信你们对这张图已经很熟悉了。flux架构最大的特色就是单向数据流,它的好处在于 可预测、易测试。
一旦将异步逻辑引入store,单向数据流被打破,应用的行为相对变得难以预测,同时单元测试的难度也会有所增长。
ps:在大部分状况下,将异步操做放在store里,简单粗暴有效,反而能够节省很多代码,看着也直观。究竟放在actions、store里,笔者是倾向于放在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: 睡觉,起床,上班
前面已经示范了如何在actions里进行异步请求,接下来简单演示下异步请求的前置步骤:参数校验。
预期中的流程是:
流程1:参数校验 --> 校验经过 --> 请求处理中 --> 请求处理成功(失败)
流程2:参数校验 --> 校验不经过 --> 请求处理失败
直接对上一小节的代码进行调整。首先判断传入的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: 睡觉
好在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,也能够有多种实现方式,具体全看判断取舍。
最后,欢迎交流。