Redux異步中間件

曾經前端的革新是以Ajax的出現爲分水嶺,現代應用中絕大部分頁面渲染會以異步流的方式進行。在Redux中,若是要發起異步請求,最合適的位置是在action creator中實現。但咱們以前瞭解到的action都是同步狀況,所以須要引入中間件讓action支持異步狀況,如異步action(異步請求)爲一個函數,或者利用promise來完成,或者是其餘自定義的形式等等,下面的middleware就是用來處理這些不一樣異步action(或者說成actionCreator)的.另外,在Redux社區裏還有其餘一些處理異步的中間件,它們大同小異,這裏就不一一分析了。javascript

redux-thunk

redux-thunk 是 redux 官方文檔中用到的異步組件,實質就是一個 redux 中間件,thunk 簡單來講 就是一個封裝表達式的函數,封裝的目的是延遲執行表達式。前端

redux-thunk 是一個通用的解決方案,其核心思想是讓 action 能夠變爲一個 thunk ,這樣的話:java

  1. 同步狀況:dispatch(action)
  2. 異步狀況:dispatch(thunk)ajax

    thunk 本質上就是一個函數,函數的參數爲 dispatch, 因此一個簡單的 thunk 異步代碼就是以下:json

    this.dispatch(function (dispatch){
        setTimeout(() => {
           dispatch({type: 'THUNK_ACTION'}) 
        }, 1000)
    })

以前已經講過,這樣的設計會致使異步邏輯放在了組件中,解決辦法爲抽象出一個 asyncActionCreator, 這裏也同樣,咱們就叫 thunkActionCreator 吧,上面的例子能夠改成:redux

export function createThunkAction(payload) {
    return function(dispatch) {
        setTimeout(() => {
           dispatch({type: 'THUNK_ACTION', payload: payload}) 
        }, 1000)
    }
}
// someComponent.js
this.dispatch(createThunkAction(payload))

redux-thunk源碼:segmentfault

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爲函數的時候,咱們並無調用next或dispatch方法,而是返回action的調用。這裏的action即爲一個Thunk函數,以達到將dispatch和getState參數傳遞到函數內的做用。api

此時,action能夠寫成thunk形式(ThunkActionCreator):數組

function getweather(url,params){
  return (dispatch,getState)=>{
    fetch(url,params)
       .then(result=>{
          dispatch({
          type:'GET_WEATHER_SUCCESS',
          payload:result,
          });
     })
     .catch(err=>{
        dispatch({
            type:'GET_WEATHER_ERROR',
            error:err,
            });
      });
};
}

redux-promise

其實 thunk 咱們已經有了處理異步的能力, 可是每次咱們要本身去手動觸發三個 action, 工做量仍是很大的。如今 ajax 不少都會包裝爲 promise 對象,,異步請求其實都是利用promise來完成的 所以咱們能夠對與 dispatch 增長一層判斷, 使得它具備處理具備 promise 屬性的 action 的能力。promise

import {isFSA} from 'flux-standard-action';

function isPromise(val){
  return next=>action=>{
  if(!isFSA(action)){
  return isPromise(action)? action.then(dispatch):next(action);
}

return isPromise(action.payload)
 ? action.payload.then(
    result=>dispatch({...action,payload:result}),
    error=>{
       dispatch({...action,payload:error,error:true});
       return Promise.reject(error);
  }
 )
 : next(action);
 };
}

思路:redux-promise兼容了FSA標準(瞭解FSA可參考https://segmentfault.com/a/11...),也就是說將返回的結果保存在payload中。實現過程很是容易理解,即判斷action或action.payload是否爲promise,若是是,就執行then,返回的結果再發送一次dispatch。

此時,action能夠寫成promise形式(promiseActionCreator):

//利用ES7的async和awaita語法
const fetchData=(url,params)=>fetch(url,params);

async function getWeather(url,params){
  const result=await fetchData(url,params);
  
  if(result.error){
     return{
       type:'GET_WEATHER_ERROR',
       error:'result.error',
       };
    }

    return{
      type:'GET_WEATHER_SUCCESS',
      payload:'result'
    };
}

redux-saga

redux-saga是redux社區一個處理異步流的後起之秀,它與上述方法最直觀的不一樣就是用generator代替了promise。的確,redux-saga是最優雅的通用解決方案,它有着靈活而強大的協程機制,能夠解決任何複雜的異步交互,具體的,放在另外一篇文章中詳細介紹。

爲action定製的自定義異步中間件

在理想狀況下,咱們不但願經過複雜的方法去請求數據,而是但願經過以下形式一併完成在異步請求過程當中的不一樣狀態:

{
  url:'/api/weather.json',
  params:{
     city:encodeURL(city),
   }
 type:['GET_WEATHER','GET_WEATHER_SUCCESS','GET_WEATHER_ERROR'],
}

能夠看到,異步請求action的格式有別於FSA。它並無使用type屬性,而使用了types屬性。在請求middleware中,會對action進行格式檢查,若存在url和types屬性,則說明這個action是一個用於發送異步請求的action。此外,並非全部請求都能攜帶參數,所以params是可選的。

const fetchMiddleware=store=>next=>action=>{
  if(!action.url || !Array.isArray(action.types)){
      return next(action);
     }
   const [LOADING,SUCCESS,ERROR]=action.types;
   
   next({
  type: LOADING,
  loading: true,
  ...action,
});

fetch(action.url,{params:action.params})
.then(result=>{
     next({
     type:SUCCES,
     loading:false,
     payload:result,
     });
 })
 .catch(err=>{
      next({
         type:ERROR,
         laoding:false,
         error:err,
         });
 });
}

使用middleware處理複雜異步流

在實際場景中,咱們不但有短鏈接請求,還有輪詢請求、多異步串聯請求,或是在異步中加入同步處理的邏輯。這時咱們須要對通常異步中間件進行處理。

輪詢

輪詢是長鏈接的一種實現方式,它可以在必定時間內從新啓動自身,而後再次發起請求。基於這個特性,咱們能夠在上一個中間件的基礎上再寫一個middleware,這裏命名爲redux-polling:

import setRafTimeout,{clearRafTimeout} from 'setRafTimeout';

export default ({dispatch,getState})=>next=>action{
  const {poolingUrl,params,types}=action;
  const isPollingAction=pollingUrl&&params&&types;
  
  if(!isPollingAction){
  return next(action);
}

let timeoutId=null;
const startPolling=(timeout=0)=>{
  timeoutId=setRafTimeout(()=>{
  const pollingAction={
  ...others,
  url:pollingUrl,
  timeoutId,
  };

  dispatch(pollingAction).then(data=>{
      if(data && data.interval && typeof data.interval=='number'){
      startPolling(data.interval*1000);
  }
else
  {
  console.error('pollingAction should fetch data contain interval');
  }
});
    },timeout);
                };
   startPolling();
 }
 
 export const clearPollingTimeout=(timeId)=>
   {
      if(timeoutId){
      clearRafTimeout(timeId);
   }
};

咱們用到了raf函數,它可讓請求在必定時間內從新啓動;startPolling函數爲遞歸函數,這樣能夠,知足輪詢的請求;在API的設計上,還暴露了clearPollingTimeout方法,以便咱們在須要時手動中止輪詢。

最後,調用action來發起輪詢:

{
pollingurl:'/api/weather.json',
params:{
         city:encodeURL(city),
      },
types:[null,'GET_WEATHER-SUCCESS',null],
}

對於長鏈接,還有其餘多種實現方式,最好的方式是對其總體作一次封裝,在內部實現諸如輪詢和WebSocket。

多異步串聯

咱們能夠經過promise封裝來實現不管是否是異步請求,均可以經過promise來傳遞以達到一個統一的效果。

const sequenceMiddlware=({dispatch,getState})=>next=>action=>{
     if(!Array.isArray(action)){
          return next(action);
           }

   return action.reduce((result,currAction)=>{
      return result.then(()=>{
        return Array.isArray(currAction)?
              Promise.all(currAction.map(item=>dispatch(item))):
              dispatch(currAction);
   });
  },Promise.resolve());
}

在構建action creator時,會傳遞一個數組,數組中每個值都是按順序執行的步驟。這裏的步驟既能夠是異步的,也能夠是同步的。在實現過程當中,咱們很是巧妙地使用了Promise.resolve()來初始化action.reduce方法,而後使用Promise.then()方法串聯起數組,達到了串聯步驟的目的。

function getCurrCity(ip){
  return 
  {
  url:'/api/getCurrCity.json',
  param: {ip},
  types: [null,'GET_CITY_SUCCESS',null],
     }
}

return getWeather(cityId){
  return
  {
  url:'/api/getWeatherInfo.json',
  param:{cityId},
  types:[null,'GET_WEATHER_SUUCCESS',null],
   }
}

function loadInitData(ip){
  return
  [
  getCurrCity(ip),
  (dispatch,state)=>{
  dispatch(getWeather(getCityIdWithState(state)));
      },
  ];
}

這種方法利用了數組的特性,它已經覆蓋了大部分場景,固然,若是串聯過程當中有不一樣的分支,就無能爲力了。

相關文章
相關標籤/搜索