redux 异步流javascript
redux-thunk前端
redux-promisejava
redux-sagagit
前面讲的 redux 中的数据流都是同步的,流程以下:github
view -> actionCreator -> action -> reducer -> newState -> container component
但同步数据不能知足真实业务开发,真实业务中异步才是主角,那如何将异步处理结合到上边的流程中呢?ajax
其实 redux 并未有和异步相关的概念,咱们能够用任何原来实现异步的方式应用到 redux 数据流中,最简单的方式就是延迟 dispatch action,以 setTimeout 为例:sql
this.dispatch({ type: 'SYNC_SOME_ACTION'})
window.setTimeout(() => {
this.dispatch({ type: 'ASYNC_SOME_ACTION' })
}, 1000)
这种方式最简单直接,可是有以下问题:shell
若是有多个相似的 action 触发场景,异步逻辑不能重用express
异步处理代码不能统一处理,最简单的例子就是节流npm
解决上面两个问题的办法很简单,把异步的代码剥离出来:
someAction.js
function dispatchSomeAction(dispatch, payload) {
// ..调用控制逻辑...
dispatch({ type: 'SYNC_SOME_ACTION'})
window.setTimeout(() => {
dispatch({ type: 'ASYNC_SOME_ACTION' })
}, 1000)
}
而后组件只须要调用:
import {dispatchSomeAction} from 'someAction.js'
dispatchSomeAction(dispatch, payload);
基于这种方式上面的流程就改成了:
view -> asyncActionDispatcher -> wait -> action -> reducer -> newState -> container component
asyncActionDispatcher 和 actionCreator 是十分相似的, 因此简单而言就能够把它理解为 asyncActionCreator , 因此新的流程为:
view -> asyncActionCreator -> wait -> action -> reducer -> newState -> container component
可是上面的方法有一些缺点
同步调用和异步调用的方式不相同:
同步的状况: store.dispatch(actionCreator(payload))
异步的状况: asyncActionCreator(store.dispatch, payload)
幸运的是在 redux 中经过 middleware 机制能够很容易的解决上面的问题
咱们已经很清楚一个 middleware 的结构 ,其核心的部分为
function(action) {
// 调用后面的 middleware
next(action)
}
middleware 彻底掌控了 reducer 的触发时机, 也就是 action 到了这里彻底由中间件控制,不乐意就不给其余中间件处理的机会,并且还能够控制调用其余中间件的时机。
举例来讲一个异步的 ajax 请求场景,能够以下实现:
function (action) {
// async call
fetch('....')
.then(
function resolver(ret) {
newAction = createNewAction(ret, action)
next(newAction)
},
function rejector(err) {
rejectAction = createRejectAction(err, action)
next(rejectAction)
})
});
}
任何异步的 javascript 逻辑均可以,如: ajax callback, Promise, setTimeout 等等, 也可使用 es7 的 async 和 await。
上面的实现方案只是针对具体的场景设计的,那若是是如何解决通用场景下的问题呢,其实目前已经有不少第三方 redux 组件支持异步 action,其中如:
这些组件都有很好的扩展性,彻底能知足咱们开发异步流程的场景,下面来一一介绍
redux-thunk 是 redux 官方文档中用到的异步组件,实质就是一个 redux 中间件,thunk 听起来是一个很陌生的词语,先来认识一下什么叫 thunk
A thunk is a function that wraps an expression to delay its evaluation.
简单来讲一个 thunk 就是一个封装表达式的函数,封装的目的是延迟执行表达式
// 1 + 2 当即被计算 = 3let x = 1 + 2;
// 1 + 2 被封装在了 foo 函数内// foo 能够被延迟执行// foo 就是一个 thunk let foo = () => 1 + 2;
redux-thunk 是一个通用的解决方案,其核心思想是让 action 能够变为一个 thunk ,这样的话:
同步状况:dispatch(action)
异步状况:dispatch(thunk)
咱们已经知道了 thunk 本质上就是一个函数,函数的参数为 dispatch, 因此一个简单的 thunk 异步代码就是以下:
this.dispatch(function (dispatch){
setTimeout(() => {
dispatch({type: 'THUNK_ACTION'})
}, 1000)
})
以前已经讲过,这样的设计会致使异步逻辑放在了组件中,解决办法为抽象出一个 asyncActionCreator, 这里也同样,咱们就叫 thunkActionCreator 吧,上面的例子能够改成:
//actions/someThunkAction.js
export function createThunkAction(payload) {
return function(dispatch) {
setTimeout(() => {
dispatch({type: 'THUNK_ACTION', payload: payload})
}, 1000)
}
}
// someComponent.jsthis.dispatch(createThunkAction(payload))
第一步:安装
$ npm install redux-thunk
第二步: 添加 thunk 中间件
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
第三步:实现一个 thunkActionCreator
//actions/someThunkAction.js
export function createThunkAction(payload) {
return function(dispatch) {
setTimeout(() => {
dispatch({type: 'THUNK_ACTION', payload: payload})
}, 1000)
}
}
第三步:组件中 dispatch thunk
this.dispatch(createThunkAction(payload));
拥有 dispatch 方法的组件为 redux 中的 container component
说了这么多,redux-thunk 是否是作了不少工做,实现起来很复杂,那咱们来看看 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;
就这么简单,只有 14 行源码,可是这简短的实现却能完成复杂的异步处理,怎么作到的,咱们来分析一下:
判断若是 action 是 function 那么执行 action(dispatch, getState, ...)
action 也就是一个 thunk
执行 action 至关于执行了异步逻辑
action 中执行 dispatch
开始新的 redux 数据流,从新回到最开始的逻辑(thunk 能够嵌套的缘由)
把执行的结果做为返回值直接返回
直接返回并无调用其余中间件,也就意味着中间件的执行在这里中止了
能够对返回值作处理(后面会讲若是返回值是 Promise 的状况)
若是不是函数直接调用其余中间件并返回
理解了这个事后是否是对 redux-thunk 的使用思路变得清晰了
根据 redux-thunk 的特性,能够作出颇有意思的事情
能够递归的 dispatch(thunk) => 实现 thunk 的组合;
thunk 运行结果会做为 dispatch返回值 => 利用返回值为 Promise 能够实现多个 thunk 的编排;
thunk 组合例子:
function thunkC() {
return function(dispatch) {
dispatch(thunkB())
}
}
function thunkB() {
return function (dispatch) {
dispatch(thunkA())
}
}
function thunkA() {
return function (dispatch) {
dispatch({type: 'THUNK_ACTION'})
}
}
Promise 例子
function ajaxCall() {
return fetch(...);
}
function thunkC() {
return function(dispatch) {
dispatch(thunkB(...))
.then(
data => dispatch(thunkA(data)),
err => dispatch(thunkA(err))
)
}
}
function thunkB() {
return function (dispatch) {
return ajaxCall(...)
}
}
function thunkA() {
return function (dispatch) {
dispatch({type: 'THUNK_ACTION'})
}
}
另一个 redux 文档中提到的异步组件为 redux-promise, 咱们直接分析一下其源码吧
import { isFSA } from 'flux-standard-action';
function isPromise(val) {
return val && typeof val.then === 'function';
}
export default function promiseMiddleware({ dispatch }) {
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);
};
}
大概的逻辑就是:
若是不是标准的 flux action,那么判断是不是 promise, 是执行 action.then(dispatch),否执行 next(action)
若是是标准的 flux action, 判断 payload 是不是 promise,是的话 payload.then 获取数据,而后把数据做为 payload 从新 dispatch({ ...action, payload: result}) , 否执行 next(action)
结合 redux-promise 能够利用 es7 的 async 和 await 语法,简化异步的 promiseActionCreator 的设计, eg:
export default async (payload) => {
const result = await somePromise;
return {
type: "PROMISE_ACTION",
payload: result.someValue;
}
}
若是对 es7 async 语法不是很熟悉能够看下面两个例子:
async 关键字能够老是返回一个 Promise 的 resolve 结果或者 reject 结果
async function foo() {
if(true)
return 'Success!';
elsethrow 'Failure!';
}
// 等价于function foo() {
if(true)
return Promise.resolve('Success!');
elsereturn Promise.reject('Failure!');
}
在 async 关键字中可使用 await 关键字,其目的是 await 一个 promise, 等待 promise resolve 和 reject
eg:
async function foo(aPromise) {
const a = await new Promise(function(resolve, reject) {
// This is only an example to create asynchronismwindow.setTimeout(
function() {
resolve({a: 12});
}, 1000);
})
console.log(a.a)
return a.a
}
// in console
> foo()
> Promise {_c: Array[0], _a: undefined, _s: 0, _d: false, _v: undefined…}
> 12
能够看到在控制台中,先返回了一个 promise,而后输出了 12
async 关键字能够极大的简化异步流程的设计,避免 callback 和 thennable 的调用,看起来和同步代码一致。
redux-saga 也是解决 redux 异步 action 的一个中间件,不过和以前的设计有本质的不一样
redux-saga 彻底基于 Es6 的 Generator Function
不使用 actionCreator 策略,而是经过监控 action, 而后在自动作处理
全部带反作用的操做(异步代码,不肯定的代码)都被放到 saga 中
redux-saga 实际也没有解释什么叫 saga ,经过引用的参考:
The term saga is commonly used in discussions of CQRS to refer to a piece of code that coordinates and routes messages between bounded contexts and aggregates.
这个定义的核心就是 CQRS-查询与责任分离 ,对应到 redux-sage 就是 action 与 处理函数的分离。 实际上在 redux-saga 中,一个 saga 就是一个 Generator 函数。
eg:
import { takeEvery, takeLatest } from 'redux-saga'
import { call, put } from 'redux-saga/effects'
import Api from '...'/* * 一个 saga 就是一个 Generator Function * * 每当 store.dispatch `USER_FETCH_REQUESTED` action 的时候都会调用 fetchUser. */function* mySaga() {
yield* takeEvery("USER_FETCH_REQUESTED", fetchUser);
}
/** * worker saga: 真正处理 action 的 saga * * USER_FETCH_REQUESTED action 触发时被调用 * @param {[type]} action [description] * @yield {[type]} [description] */function* fetchUser(action) {
try {
const user = yield call(Api.fetchUser, action.payload.userId);
yield put({type: "USER_FETCH_SUCCEEDED", user: user});
} catch (e) {
yield put({type: "USER_FETCH_FAILED", message: e.message});
}
}
watcher saga
负责编排和派发任务的 saga
worker saga
真正负责处理 action 的函数
saga helper
如上面例子中的 takeEvery,简单理解就是用于监控 action 并派发 action 到 worker saga 的辅助函数
Effect
redux-saga 彻底基于 Generator 构建,saga 逻辑的表达是经过 yield javascript 对象来实现,这些对象就是Effects。
这些对象至关于描述任务的规范化数据(任务如执行异步函数,dispatch action 到一个 store),这些数据被发送到 redux-saga 中间件中执行,如:
put({type: "USER_FETCH_SUCCEEDED", user: user})
表示要执行 dispatch({{type: "USER_FETCH_SUCCEEDED", user: user}})
任务
call(fetch, url)
表示要执行 fetch(url)
经过这种 effect 的抽象,能够避免 call 和 dispatch 的当即执行,而是描述要执行什么任务,这样的话就很容易对 saga 进行测试,saga 所作的事情就是将这些 effect 编排起来用于描述任务,真正的执行都会放在 middleware 中执行。
第一步:安装
$ npm install --save redux-saga
第二步:添加 saga 中间件
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'// 建立 saga 中间件const sagaMiddleware = createSagaMiddleware()
// 添加到中间件中const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
// 当即运行 saga ,让监控器开始监控
sagaMiddleware.run(mySaga)
第三步:定义 sagas/index.js
import { takeEvery } from 'redux-saga'
import { put } from 'redux-saga/effects'
export const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
// 将异步执行 increment 任务
export function* incrementAsync() {
yield delay(1000)
yield put({ type: 'INCREMENT' })
}
// 在每一个 INCREMENT_ASYNC action 调用后,派生一个新的 incrementAsync 任务
export default function* watchIncrementAsync() {
yield* takeEvery('INCREMENT_ASYNC', incrementAsync)
}
第四步:组件中调用
this.dispatch({type: 'INCREMENT_ASYNC'})
redux-saga 基于 Generator 有不少高级的特性, 如:
基于 take Effect 实现更自由的任务编排
fork 和 cancel 实现非阻塞任务
并行任何和 race 任务
saga 组合 ,yield* saga