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,单向数据流被打破,应用的行为相对变得难以预测,同时单元测试的难度也会有所增加。

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

最后,欢迎交流。

时间: 2024-09-06 09:19:28

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

用 eric6 与 PyQt5 实现python的极速GUI编程(系列01)--Hello world!

  [题记] 我是一个菜鸟,这个系列是我的学习笔记. PyQt5 出来有一段时间了, PyQt5 较之 PyQt4 有一些变化,而网上流传的几乎都是 PyQt4 的教程,照搬的话大多会出错. eric6 也是刚刚出来,eric6 与 PyQt5 结合的教程网上几乎没有. 本人也是一边学习,一边摸索,并记录与此,希望帮到有需要的朋友,更希望路过的大侠多多指教为好.   [引子] 用 eric6 与 PyQt5 结合,非常方便的实现界面与逻辑分离,满足python的极速GUI编程,你只需要关注程序的

Java 集合系列01之 总体框架

声明:该分类是转自skywang12345的相关博文.纯粹是学习之用.无商业动机.     Java集合是java提供的工具包,包含了常用的数据结构:集合.链表.队列.栈.数组.映射等.Java集合工具包位置是java.util.*Java集合主要可以划分为4个部分:List列表.Set集合.Map映射.工具类(Iterator迭代器.Enumeration枚举类.Arrays和Collections)..Java集合工具包框架图(如下): 大致说明: 看上面的框架图,先抓住它的主干,即Coll

Redux系列01:从一个简单例子了解action、store、reducer

其实,redux的核心概念就是store.action.reducer,从调用关系来看如下所示 store.dispatch(action) --> reducer(state, action) --> final state 可以先看下面的极简例子有个感性的认识,下面会对三者的关系进行简单介绍 // reducer方法, 传入的参数有两个 // state: 当前的state // action: 当前触发的行为, {type: 'xx'} // 返回值: 新的state var reduc

(DirectX系列01)DirectSound 3D语音特效

       近日正在学习DirectX,主要用于视频监控和流媒体方面应用,学习<Visual C++音视频处理技术和工程实践>已经有大半,一直想写些感受,兴趣所致,今天重新学习了下DirectSound 3D语音特效,并编写一些代码,在此分享出来,希望对大家能有所帮助.        大家都有这种感觉,当我们离发声源越来越远的时候,声音越来越小.DirectSound就是模仿这些现象,从数学理论角度加以描述.当然,影响音效的因素不光只有这些,还有如Doppler效应等,但是DirectSou

Redux系列02:一个炒鸡简单的react+redux例子

前言 在<Redux系列01:从一个简单例子了解action.store.reducer>里面,我们已经对redux的核心概念做了必要的讲解.接下来,同样是通过一个简单的例子,来讲解如何将redux跟react应用结合起来. 我们知道,在类flux框架设计中,单向数据流转的方向无非如下: 转换成redux的语言,就是这个样子.接下来就看实际例子,一个简单到不存在实用价值的todo list. 例子:实际运行效果 本文的代码示例可以在github上下载,点击查看.README里有详细的运行步骤,

java io系列20之 PipedReader和PipedWriter

本章,我们学习PipedReader和PipedWriter.它们和"PipedInputStream和PipedOutputStream"一样,都可以用于管道通信. PipedWriter 是字符管道输出流,它继承于Writer.PipedReader 是字符管道输入流,它继承于Writer.PipedWriter和PipedReader的作用是可以通过管道进行线程间的通讯.在使用管道通信时,必须将PipedWriter和PipedReader配套使用. 转载请注明出处:http:/

Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)

概要 前面,我们学完了List的全部内容(ArrayList, LinkedList, Vector, Stack). Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例  Java 集合系列04之 fail-fast总结(通过ArrayList来说明fail-fast的原理.解决办法)  Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例 Java 集合系列06之 Vector详细介绍(源码解析)和使用示例 Java 集合系列07之 Stack

java io系列26之 RandomAccessFile

本文主要介绍 RandomAccessFile. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_26.html 更多内容请参考:java io系列01之 "目录"   RandomAccessFile RandomAccessFile 是随机访问文件(包括读/写)的类.它支持对文件随机访问的读取和写入,即我们可以从指定的位置读取/写入文件数据.需 要注意的是,RandomAccessFile 虽然属于java.io包,但它不是Inpu

java io系列21之 InputStreamReader和OutputStreamWriter

InputStreamReader和OutputStreamWriter 是字节流通向字符流的桥梁:它使用指定的 charset 读写字节并将其解码为字符.InputStreamReader 的作用是将"字节输入流"转换成"字符输入流".它继承于Reader.OutputStreamWriter 的作用是将"字节输出流"转换成"字符输出流".它继承于Writer. 转载请注明出处:http://www.cnblogs.com/