曾经前端的革新是以Ajax的出现为分水岭,现代应用中绝大部分页面渲染会以异步流的方式进行。在Redux中,若是要发起异步请求,最合适的位置是在action creator中实现。但咱们以前了解到的action都是同步状况,所以须要引入中间件让action支持异步状况,如异步action(异步请求)为一个函数,或者利用promise来完成,或者是其余自定义的形式等等,下面的middleware就是用来处理这些不一样异步action(或者说成actionCreator)的.另外,在Redux社区里还有其余一些处理异步的中间件,它们大同小异,这里就不一一分析了。javascript
redux-thunk 是 redux 官方文档中用到的异步组件,实质就是一个 redux 中间件,thunk 简单来讲 就是一个封装表达式的函数,封装的目的是延迟执行表达式。前端
redux-thunk 是一个通用的解决方案,其核心思想是让 action 能够变为一个 thunk ,这样的话:java
异步状况: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, }); }); }; }
其实 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社区一个处理异步流的后起之秀,它与上述方法最直观的不一样就是用generator代替了promise。的确,redux-saga是最优雅的通用解决方案,它有着灵活而强大的协程机制,能够解决任何复杂的异步交互,具体的,放在另外一篇文章中详细介绍。
在理想状况下,咱们不但愿经过复杂的方法去请求数据,而是但愿经过以下形式一并完成在异步请求过程当中的不一样状态:
{ 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,这里命名为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函数,它可让请求在必定时间内从新启动;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))); }, ]; }
这种方法利用了数组的特性,它已经覆盖了大部分场景,固然,若是串联过程当中有不一样的分支,就无能为力了。