redux中的异步

兴国安邦
• 阅读 4664

关于异步

无论是前端开发还是后端的nodejs,异步基本上总是核心。我所在的公司是做大数据处理的,经常需要从后台拿取大量的数据,导致整个请求会比较耗时。如下一个场景:
redux中的异步
我选择A应用,点击搜索,这时候我马上去选择应用B,点击搜索,可能的情况是,A的数据量比B的数据量大,导致A的结果是你最后获取到的,这是就会出现这样的问题:此时用户的搜索条件是B,但你展示的结果确是A。代码是类似这样的:

const request = (api, time) => {
  return new Promise(resolve => {
    setTimeout(() => resolve(`data ${api}!`), time);
  })
};

const requestA = () => request('A', 4000);
const requestB = () => request('B', 2000);

let result;
requestA().then(data => {
  result = data;
});
// 接着
requestB().then(data => {
  result = data;
});
// 4秒后打印result
console.log(result);
// 打印出 'data A!'

实际工作中,比较简单的方式大概就是,在请求A的时候给查询按钮设置成disabled,请求返回后再去放开按钮,去请求B。但是体验可能会不太好,比如用户搜了A,A的搜索条件比较广,导致用户一直等到timeout的情况才能去继续查询。
我们可以用闭包去解决这个问题:

let _id = 0;
let _req_id = 0;
const request = (api, time) => {
  _id++;
  return new Promise(resolve => {
    setTimeout(() => resolve(`data ${api}!`), time);
  })
};

const requestA = () => request('A', 4000);
const requestB = () => request('B', 2000);

let result;
void function(id) {
  requestA().then(data => {
    if (id === _id) {
      result = data;
    }
  });
}(++_req_id);
// 接着
void function(id) {
  requestB().then(data => {
    if (id === _id) {
      result = data;
    }
  });
}(++_req_id);
// 4秒后打印result
console.log(result);
// 打印出 'data B!'

我在angular1.5中碰到这种问题一般都是这样解决的。
(以后面试官问你闭包都有啥用的时候可以举这个栗子,比那些星期一吃包子,星期二吃馒头的栗子好多了2333)

const plan = day => food => console.log(`${day}吃${food}`);
const SundayPlan = plan('星期天');
SundayPlan('包子');
SundayPlan('馒头');

redux中的异步

用过react的人对redux一般多少都有一定了解,毕竟是作为react社区最热门的状态管理框架,相信不少人也是用过。redux并不能开箱即用,在异步上还需要依赖社区的第三方库。

redux-thunk

出自redux的作者Dan,相信这个不少人都使用,相对于其他方案,使用起来比较简单。对于不太复杂的场景使用起来还是很方便的。使用起来就像下面的样子:

const GET_TOPICS_REQUEST = 'GET_TOPICS_REQUEST',
  GET_TOPICS_SUCCESS = 'GET_TOPICS_SUCCESS',
  GET_TOPICS_FAILED = 'GET_TOPICS_FAILED';
export const getTopics = (query = defaultQuery) => (dispatch) => {
  dispatch({
    type: GET_TOPICS_REQUEST,
    isPending: true,
  });
  axios.get('').then(() => {
    dispatch({
      type: GET_TOPICS_SUCCESS,
      isPending: false,
    });
  }).catch(err => {
    dispatch({
      type: GET_TOPICS_FAILED,
      isPending: false,
    });
  });
}

使用起来相当简单,但其中会出现像我们刚开始提到的问题,action是没法取消的。打个比方,我先请求A,然后立刻请求B,是相同的action,B的数据先返回,A的数据后返回,最后state更新的数据就变成了A。可行的解决方式是 redux-thunk + async/await。但async/await这玩意用在前端,现在不是很推荐。

redux-observable

基于Rxjs。
redux中的异步
官方文档上的说明。我不会,我不知道,再见?...
没用过,就不做讨论了。

redux-saga

在官网上看了一下,没错,这就是我想要的。
文档:中文官方
需要注意的是,中文的文档已经落后官方文档了,看的话推荐官方文档,我这种英语渣是结合起来看的。如果你的英语够好,可以作出一些贡献,传送门
最简单的方式,实现一个redux-logger:

// StoreConfig
import { createStore, compose, applyMiddleware } from 'redux';
import createSagaMiddleware, { END } from 'redux-saga';
import { xx } from '../reducers';

const StoreConfig = (initialState) => {
  const sagaMiddleware = createSagaMiddleware();

  const store = createStore(
    xx,
    initialState,
    compose(
      applyMiddleware(sagaMiddleware),
      window['devToolsExtension'] ? window['devToolsExtension']() : f => f,
    ),
  );
  store.runSaga = sagaMiddleware.run;
  store.close = () => store.dispatch(END);

  return store;
};

export default StoreConfig;

启动redux-saga:

import rootSaga from './sagas';
import StoreConfig from './store/index';

const store = StoreConfig(initialState);
store.runSaga(rootSaga);

saga/index.js:

import { take, all, fork, select } from 'redux-saga/effects';
import { api } from '../services';

function* watchAndLog() {
  while (true) {
    const action = yield take('*');
    const getState = yield select(state => state);
    console.log('%caction---', 'color: green;', action);
    console.log('%cstate after---', 'color: green;', getState);
  }
}

export default function* root() {
  yield all([
    fork(watchAndLog),
  ]);
}

redux中的异步
redux-logger的效果。

redux-saga的优点:
  1. 对异步流优秀的控制,对于文中一开始提到的问题,我们有更好的解决方式。文档中给了解决方案。

    如果我们只想得到最新那个请求的响应(例如,始终显示最新版本的数据)。我们可以使用 takeLatest辅助函数。
      import { takeLatest } from 'redux-saga'
    
      fetchData () {
        // ...
      }
    
      function* watchFetchData() {
        yield* takeLatest('FETCH_REQUESTED', fetchData)
      }

    在任何时刻 takeLatest 只允许执行一个 fetchData 任务。并且这个任务是最后被启动的那个。 如果之前已经有一个任务在执行,那之前的这个任务会自动被取消。

  2. 无阻塞的调用,可以fork一个独立的“线程”,并可以通过cancel取消。如登陆,在登陆未完成时点击登出,将登陆的线程取消:

    function* authorize(username, password) {
      try {
        const { token } = yield call(api.userLogin, { username, password });
        yield put({type: LOGIN_SUCCESS, isLoginPending: false, token });
        localStorage.setItem('SAGA-TOKEN', token);
      } catch (error) {
        yield put({type: LOGIN_FAILURE, isLoginPending: false, error});
      } finally {
        if (yield cancelled()) {
          console.log('%cwow, killed the task!', 'color: red;');
        }
      }
    }
    
    function* loginFlow() {
      while (true) {
        const { username, password } = yield take(LOGIN_REQUEST);
        const task = yield fork(authorize, username, password);
        const action = yield take([LOGIN_OUT, LOGIN_FAILURE]);
        if (action.type === LOGIN_OUT) {
          localStorage.removeItem('SAGA-TOKEN');
          yield cancel(task);
        }
      }
    }

    点击登陆,未完成请求时,立即点击登出,状态变化停留在登出:
    redux中的异步

redux-saga还有其他很多功能,如同时执行多个任务等,更多的内容建议看官方的文档。

其他的异步方式

redux-promiseredux-promise-middleware这些,不是很推荐。

总结

异步流一直是比较烦人的点,在redux中也是。综合使用的来看,推荐用redux-saga或者redux-observable(如果熟悉Rxjs的话)。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
layim的websocket消息撤回功能实现
我的大概思路就是,前端根据选取的内容获得他的cid,我的cid是js生成的uuid,然后:1、通过websocket广播给对应的人去删除localstorage里的缓存,2、ajax异步请求删除数据库里的数据记录3、如果对方此时也打开了聊天面板就要用jquery找到那条消息然后remove。由于目前发现layim3.6版本并没有给自己
Stella981 Stella981
3年前
Android异步操作总结
Android中经常会有一些操作比如网络请求,文件读写,数据库操作,比较耗时,我们需要将其放在非UI线程去处理,此时,我们需要处理任务前后UI的变化和交互。我们需要通过类似js中异步请求处理,这里总结我所了解到的,方便自己记忆,也方便别人的浏览。1.AsyncTasknewAysncTask().execute();AsyncTask会
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
3年前
Java中使用HTTP阻塞式调用服务器API
应用场景:前端页面点击刷新,调用服务器A上Java接口,然后A调用服务器B的后台Python接口实时刷新后台数据库。在这个场景中会涉及到两个问题:异步,Python服务器压力(一)解决Python服务器压力如果Python服务器接口不做任何措施,那么可能会有恶意的访问,从而导致该服务器一直刷新后台数据库。我的解决方式是:服务器B会提供一串字符
Stella981 Stella981
3年前
Ajax异步请求
Ajax即"AsynchronousJavascriptAndXML"(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术。Ajax异步JavaScript和XML(标准通用标记语言的子集)。通过在后台与服务器进行少量数据交换,Ajax可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况
Stella981 Stella981
3年前
Jupyter notebook使用技巧大全
点击上方“蓝字”,轻松关注!(https://oscimg.oschina.net/oscnet/3a406a00d29b44568aebb8be9d319d3b.gif)JupyterNotebook简介JupyterNotebook是一款开源的web应用,它允许使用者创建和分享包含代码,公式,可
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(