Redux异步中间件
曾經(jīng)前端的革新是以Ajax的出現(xiàn)為分水嶺,現(xiàn)代應(yīng)用中絕大部分頁面渲染會以異步流的方式進(jìn)行。在Redux中,如果要發(fā)起異步請求,最合適的位置是在action creator中實現(xiàn)。但我們之前了解到的action都是同步情況,因此需要引入中間件讓action支持異步情況,如異步action(異步請求)為一個函數(shù),或者利用promise來完成,或者是其他自定義的形式等等,下面的middleware就是用來處理這些不同異步action(或者說成actionCreator)的.另外,在Redux社區(qū)里還有其他一些處理異步的中間件,它們大同小異,這里就不一一分析了。
redux-thunk
redux-thunk 是 redux 官方文檔中用到的異步組件,實質(zhì)就是一個 redux 中間件,thunk 簡單來說 就是一個封裝表達(dá)式的函數(shù),封裝的目的是延遲執(zhí)行表達(dá)式。
redux-thunk 是一個通用的解決方案,其核心思想是讓 action 可以變?yōu)橐粋€ thunk ,這樣的話:
異步情況:dispatch(thunk)
thunk 本質(zhì)上就是一個函數(shù),函數(shù)的參數(shù)為 dispatch, 所以一個簡單的 thunk 異步代碼就是如下:
this.dispatch(function (dispatch){setTimeout(() => {dispatch({type: 'THUNK_ACTION'}) }, 1000) })之前已經(jīng)講過,這樣的設(shè)計會導(dǎo)致異步邏輯放在了組件中,解決辦法為抽象出一個 asyncActionCreator, 這里也一樣,我們就叫 thunkActionCreator 吧,上面的例子可以改為:
export function createThunkAction(payload) {return function(dispatch) {setTimeout(() => {dispatch({type: 'THUNK_ACTION', payload: payload}) }, 1000)} } // someComponent.js this.dispatch(createThunkAction(payload))redux-thunk源碼:
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;思路:當(dāng)action為函數(shù)的時候,我們并沒有調(diào)用next或dispatch方法,而是返回action的調(diào)用。這里的action即為一個Thunk函數(shù),以達(dá)到將dispatch和getState參數(shù)傳遞到函數(shù)內(nèi)的作用。
此時,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?我們已經(jīng)有了處理異步的能力, 但是每次我們要自己去手動觸發(fā)三個?action, 工作量還是很大的?,F(xiàn)在?ajax?很多都會包裝為?promise?對象,,異步請求其實都是利用promise來完成的 因此我們可以對與?dispatch?增加一層判斷, 使得它具有處理具有?promise?屬性的?action?的能力。
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標(biāo)準(zhǔn)(了解FSA可參考https://segmentfault.com/a/11...),也就是說將返回的結(jié)果保存在payload中。實現(xiàn)過程非常容易理解,即判斷action或action.payload是否為promise,如果是,就執(zhí)行then,返回的結(jié)果再發(fā)送一次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社區(qū)一個處理異步流的后起之秀,它與上述方法最直觀的不同就是用generator代替了promise。的確,redux-saga是最優(yōu)雅的通用解決方案,它有著靈活而強大的協(xié)程機制,可以解決任何復(fù)雜的異步交互,具體的,放在另一篇文章中詳細(xì)介紹。
為action定制的自定義異步中間件
在理想情況下,我們不希望通過復(fù)雜的方法去請求數(shù)據(jù),而是希望通過如下形式一并完成在異步請求過程中的不同狀態(tài):
{url:'/api/weather.json',params:{city:encodeURL(city),}type:['GET_WEATHER','GET_WEATHER_SUCCESS','GET_WEATHER_ERROR'], }可以看到,異步請求action的格式有別于FSA。它并沒有使用type屬性,而使用了types屬性。在請求middleware中,會對action進(jìn)行格式檢查,若存在url和types屬性,則說明這個action是一個用于發(fā)送異步請求的action。此外,并不是所有請求都能攜帶參數(shù),因此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處理復(fù)雜異步流
在實際場景中,我們不但有短連接請求,還有輪詢請求、多異步串聯(lián)請求,或是在異步中加入同步處理的邏輯。這時我們需要對一般異步中間件進(jìn)行處理。
輪詢
輪詢是長連接的一種實現(xiàn)方式,它能夠在一定時間內(nèi)重新啟動自身,然后再次發(fā)起請求。基于這個特性,我們可以在上一個中間件的基礎(chǔ)上再寫一個middleware,這里命名為redux-polling:
import setRafTimeout,{clearRafTimeout} from 'setRafTimeout';export default ({dispatch,getState})=>next=>action{const {poolingUrl,params,types}=action;const isPollingAction=pollingUrl&¶ms&&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函數(shù),它可以讓請求在一定時間內(nèi)重新啟動;startPolling函數(shù)為遞歸函數(shù),這樣可以,滿足輪詢的請求;在API的設(shè)計上,還暴露了clearPollingTimeout方法,以便我們在需要時手動停止輪詢。
最后,調(diào)用action來發(fā)起輪詢:
{ pollingurl:'/api/weather.json', params:{city:encodeURL(city),}, types:[null,'GET_WEATHER-SUCCESS',null], }對于長連接,還有其他多種實現(xiàn)方式,最好的方式是對其整體做一次封裝,在內(nèi)部實現(xiàn)諸如輪詢和WebSocket。
多異步串聯(lián)
我們可以通過promise封裝來實現(xiàn)不論是否是異步請求,都可以通過promise來傳遞以達(dá)到一個統(tǒng)一的效果。
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()); }在構(gòu)建action creator時,會傳遞一個數(shù)組,數(shù)組中每一個值都是按順序執(zhí)行的步驟。這里的步驟既可以是異步的,也可以是同步的。在實現(xiàn)過程中,我們非常巧妙地使用了Promise.resolve()來初始化action.reduce方法,然后使用Promise.then()方法串聯(lián)起數(shù)組,達(dá)到了串聯(lián)步驟的目的。
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)));},]; }這種方法利用了數(shù)組的特性,它已經(jīng)覆蓋了大部分場景,當(dāng)然,如果串聯(lián)過程中有不同的分支,就無能為力了。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的Redux异步中间件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入学习consul
- 下一篇: 大话微服务(Big Talk in Mi