内容来自 DOC https://q.houxu6.top/?s=使用ES6生成器(Generators)和redux-saga与使用ES2017的async/await和redux-thunk相比的优缺点。
目前关于redux的最新讨论焦点是redux-saga/redux-saga。它使用生成器函数来监听/分发actions。
在我深入研究之前,我想了解使用redux-saga
与下面使用redux-thunk
和async/await的方法相比的优缺点。
一个组件可能如下所示,像往常一样分发actions。
import { login } from 'redux/auth';
class LoginForm extends Component {
onClick(e) {
e.preventDefault();
const { user, pass } = this.refs;
this.props.dispatch(login(user.value, pass.value));
}
render() {
return (<div>
<input type="text" ref="user" />
<input type="password" ref="pass" />
<button onClick={::this.onClick}>Sign In</button>
</div>);
}
}
export default connect((state) => ({}))(LoginForm);
然后我的actions看起来像这样:
// auth.js
import request from 'axios';
import { loadUserData } from './user';
// 定义常量
// 定义初始状态
// 导出默认reducer
export const login = (user, pass) => async (dispatch) => {
try {
dispatch({ type: LOGIN_REQUEST });
let { data } = await request.post('/login', { user, pass });
await dispatch(loadUserData(data.uid));
dispatch({ type: LOGIN_SUCCESS, data });
} catch(error) {
dispatch({ type: LOGIN_ERROR, error });
}
}
// 更多actions...
// user.js
import request from 'axios';
// 定义常量
// 定义初始状态
// 导出默认reducer
export const loadUserData = (uid) => async (dispatch) => {
try {
dispatch({ type: USERDATA_REQUEST });
let { data } = await request.get(`/users/${uid}`);
dispatch({ type: USERDATA_SUCCESS, data });
} catch(error) {
dispatch({ type: USERDATA_ERROR, error });
}
}
// 更多actions...
在redux-saga中,与上面的示例等效的代码如下:
export function* loginSaga() {
while(true) {
const { user, pass } = yield take(LOGIN\_REQUEST)
try {
let { data } = yield call(request.post, '/login', { user, pass });
yield fork(loadUserData, data.uid);
yield put({ type: LOGIN\_SUCCESS, data });
} catch(error) {
yield put({ type: LOGIN\_ERROR, error });
}
}
}
export function* loadUserData(uid) {
try {
yield put({ type: USERDATA\_REQUEST });
let { data } = yield call(request.get, `/users/${uid}`);
yield put({ type: USERDATA\_SUCCESS, data });
} catch(error) {
yield put({ type: USERDATA\_ERROR, error });
}
}
首先要注意的是,我们使用yield call(func, ...args)
的形式调用API函数。call
并不执行实际的效果,它只是创建一个普通对象,类似于{type: 'CALL', func, args}
。执行任务的工作委托给redux-saga中间件,它负责执行函数并恢复生成器并返回结果。
主要优点是您可以在Redux之外使用简单的相等性检查来测试生成器。
const iterator = loginSaga()
assert.deepEqual(iterator.next().value, take(LOGIN\_REQUEST))
// 使用一些虚拟的操作恢复生成器
const mockAction = {user: '...', pass: '...'}
assert.deepEqual(
iterator.next(mockAction).value,
call(request.post, '/login', mockAction)
)
// 模拟错误结果
const mockError = 'invalid user/password'
assert.deepEqual(
iterator.throw(mockError).value,
put({ type: LOGIN\_ERROR, error: mockError })
)
请注意,我们通过向迭代器的next
方法注入模拟数据来模拟API调用结果。与模拟函数相比,模拟数据要简单得多。
第二个要注意的是对yield take(ACTION)
的调用。Thunk是由动作创建者在每个新动作(例如LOGIN_REQUEST
)上调用的。即动作持续地“推送”给thunk,而thunk无法控制何时停止处理那些动作。
在redux-saga中,生成器“拉取”下一个动作。也就是说,它们可以控制何时监听某个动作以及何时不监听。在上面的示例中,流程指令放置在一个while(true)
循环内,因此它将监听每个传入的动作,从某种程度上模拟了thunk的推送行为。
拉取的方式允许实现复杂的控制流程。例如,假设我们想要添加以下要求:
- 处理LOGOUT用户动作
- 在首次成功登录时,服务器返回一个以某个字段
expires\_in
存储的一段时间后过期的令牌,我们需要在每个expires\_in
毫秒上后台刷新授权 - 注意,在等待api调用的结果时(无论是初始登录还是刷新),用户可能会在其间注销。
如何使用thunk实现这一点,并为整个流程提供完整的测试覆盖率?以下是使用Sagas可能的实现方式:
function* authorize(credentials) {
const token = yield call(api.authorize, credentials)
yield put( login.success(token) )
return token
}
function* authAndRefreshTokenOnExpiry(name, password) {
let token = yield call(authorize, {name, password})
while(true) {
yield call(delay, token.expires\_in)
token = yield call(authorize, {token})
}
}
function* watchAuth() {
while(true) {
try {
const {name, password} = yield take(LOGIN\_REQUEST)
yield race([
take(LOGOUT),
call(authAndRefreshTokenOnExpiry, name, password)
])
// 用户已注销,下一个while迭代将等待下一个LOGIN\_REQUEST动作
} catch(error) {
yield put( login.error(error) )
}
}
}
在上面的示例中,我们使用race
来表达我们的并发需求。如果take(LOGOUT)
赢得了比赛(即用户点击了注销按钮),比赛将自动取消authAndRefreshTokenOnExpiry
后台任务。如果authAndRefreshTokenOnExpiry
在call(authorize, {token})
调用的中间被阻塞,它也会被取消。取消会自动向下传播。
您可以在此流程的可运行演示中找到。
标签:ES6,生成器,yield,call,error,LOGIN,redux,type From: https://www.cnblogs.com/xiaomandujia/p/17829068.html