异步方案选型redux-saga 和 redux-thunk(async/await)

时间:2022-01-22 23:41:38

简要介绍:

redux中的action仅支持原始对象(plain object),处理有副作用的action,需要使用中间件。中间件可以在发出action,到reducer函数接受action之间,执行具有副作用的操作。

之前一直使用redux-thunk处理异步等副作用操作,在action中处理异步等副作用操作,此时的action是一个函数,以dispatch,getState作为形参,函数体内的部分可以执行异步。通过redux-thunk来处理异步,action可谓是多种多样,不利于维护。

本文主要介绍一下redux-saga ,并在此基础上比较redux-thunk(async/await)

1 . redux-thunk 的使用与缺点

(1)redux-thunk的使用

thunkredux作者给出的中间件,实现极为简单,10多行代码:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

这几行代码做的事情也很简单,判别action的类型,如果action是函数,就调用这个函数,调用的步骤为:

action(dispatch, getState, extraArgument);

发现实参为dispatch和getState,因此我们在定义action为thunk函数是,一般形参为dispatch和getState。

(2)redux-thunk的缺点

thunk的缺点也是很明显的,thunk仅仅做了执行这个函数,并不在乎函数主体内是什么,也就是说thunk使得redux可以接受函数作为action,但是函数的内部可以多种多样。比如下面是一个获取商品列表的异步操作所对应的action

export default ()=>(dispatch)=>{
    fetch('/api/goodList',{ //fecth返回的是一个promise
      method: 'get',
      dataType: 'json',
    }).then(function(json){
      var json=JSON.parse(json);
      if(json.msg==200){
        dispatch({type:'init',data:json.data});
      }
    },function(error){
      console.log(error);
    });
};

从这个具有副作用的action中,我们可以看出,函数内部极为复杂。如果需要为每一个异步操作都如此定义一个action,显然action不易维护。

action不易维护的原因:

I)action的形式不统一

II)就是异步操作太为分散,分散在了各个action中

2 .redux-saga的使用

redux-saga中,action是plain object(原始对象),并且集中处理了所有的异步操作,下面我们以redux-saga的官方例子shopping-cart为例,来说说redux-saga的使用。

shopping-cart例子很简单,展示的是如下过程:

商品列表——>添加商品——>购物车——>付款

具体的页面,如下:
异步方案选型redux-saga 和 redux-thunk(async/await)

显然,这里有两个明显的异步操作需要执行:

获取商品列表和付款

getAllProducts()checkout()来表示,如果用thunk,那么这两个异步的操作分属于两个不同的action中,但是在saga中,它们是集中处理的。

使用saga,我们先生成一个集中处理异步的saga.js文件:

...
...
export function* getAllProducts() {
  const products = yield call(api.getProducts)
  yield put(actions.receiveProducts(products))
}

export function* checkout() {
  try {
    const cart = yield select(getCart)
    yield call(api.buyProducts, cart)
    yield put(actions.checkoutSuccess(cart))
  } catch (error) {
    yield put(actions.checkoutFailure(error))
  }
}
...
...

抛去其他部分(具体用法我们待会解释),我们看到在saga.js中集中了这两个异步操作getAllProducts()checkout()

此外,在saga中的action跟原始对象是完全相同的,我们来看saga中的action creator :

export const GET_ALL_PRODUCTS = 'GET_ALL_PRODUCTS'
export function getAllProducts() {
  return {
    type: GET_ALL_PRODUCTS,
  }
}

上述的action creator中,创建的action是一个plain object,跟我们在redux中同步action的样式是统一的。

3 . redux-saga的API

redux-saga是通过ES6中的generator实现的(babel的基础版本不包含generator语法,因此需要在使用saga的地方import ‘babel-polyfill’)。redux-saga本质是一个可以自执行的generator。

(1)redux-saga中的Effect

redux-saga中定义了Effect,Effect是什么呢,本质就是一个特定的函数,返回的是纯文本对象。简单理解,通过Effect函数,会返回一个字符串,saga-middleware根据这个字符串来执行真正的异步操作,可以具体表现成如下形式:

异步操作——>Effect函数——>纯文本对象——>saga-middleware——>执行异步操作

因为Effect的存在,方便saga测试异步操作。

(2)Effect具体函数

Effect函数有很多个,在redux-saga/effects提供,主要包括call,fork,put,take,select等,它们都与middleware中的操作一一对应。

I)call 和 fork

call和fork表示异步调用,其中call表示的是阻塞调用,fork表示的是非阻塞调用。

II)put和select

put对应的是middleware中的dispatch方法,参数是一个plain object,一般在异步调用返回结果后,接着执行put。select相当于getState,用于获取store中的相应部分的state。

III)take、takeEvery、takeLatest

redux-saga中如果在非阻塞调用下(fork),那么遵循的是worker/watcher模式,通过take可以监听某个action是否被发起,此外通过take结合fork,可以实现takeEvery和takeLatest的效果。

如果一个异步操作的action被发起多次,takeEvery会执行多次action,而takeLatest只会执行最近的一次。

4 . redux-saga的优缺点

优点:

(1)集中处理了所有的异步操作,异步接口部分一目了然

(2)action是普通对象,这跟redux同步的action一模一样

(3)通过Effect,方便异步接口的测试

(4)通过worker 和watcher可以实现非阻塞异步调用,并且同时可以实

现非阻塞调用下的事件监听

(5) 异步操作的流程是可以控制的,可以随时取消相应的异步操作。

缺点:太复杂,学习成本较高

5 .redux-thunk(async/await)

如果在redux-thunk中,action是一个async函数,通过约定一些异步操作时所遵循的规则,即使不能集中展示所有的异步操作,但是通过约定我们可以减少函数action的复杂度,我们最初获取商品列表的那个action,可以改写成:

export default function(){
  return  function(dispatch){
    await result=fetch(...)
    result.then(...)
  }
};

总之,如果不涉及学习成本等,react-saga还是比较推荐使用的。