• Reflux系列01:异步操作经验小结


    写在前面

    在实际项目中,应用往往充斥着大量的异步操作,如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,单向数据流被打破,应用的行为相对变得难以预测,同时单元测试的难度也会有所增加。

    enter image description here

    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,也可以有多种实现方式,具体全看判断取舍。

    最后,欢迎交流。

  • 相关阅读:
    (转载)Android content provider基础与使用
    如何解决Android的SDK与ADT不匹配问题
    Android 中断线程的处理
    用AsyncTask 来实现下载图片在android开发中
    开源自己的一个小android项目(美女撕衣服游戏)
    实现在Android开发中的Splash Screen开场屏的效果
    支持在安卓中UI(View)的刷新功能
    android从资源文件中读取文件流显示
    后缀数组 模板题 hdu1403(最长公共(连续)子串)
    Codeforces Round #383 (Div. 1) C(二分图)
  • 原文地址:https://www.cnblogs.com/chyingp/p/reflux-async-action.html
Copyright © 2020-2023  润新知