reference Link
takeEvery takeLalest
takeEvery
允许多个 fetchData
实例同时启动,在某个特定时刻,尽管之前还有一个或多个 fetchData
尚未结束,我们还是可以启动一个新的 fetchData
任务,
takeEvery
不同,在任何时刻 takeLatest
只允许一个 fetchData
任务在执行。并且这个任务是最后被启动的那个。 如果已经有一个任务在执行的时候启动另一个 fetchData
,那之前的这个任务会被自动取消。
下面创建一个 Saga 来监听所有的 USER_FETCH_REQUESTED
action,并触发一个 API 调用获取用户数据
// sagas.js
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'
// worker Saga : 将在 USER_FETCH_REQUESTED action 被 dispatch 时调用
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});
}
}
/*
在每个 `USER_FETCH_REQUESTED` action 被 dispatch 时调用 fetchUser
允许并发(译注:即同时处理多个相同的 action)
*/
function* mySaga() {
yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
// 监听指定action 并在dispatch('USER_FETCH_REQUESTED') 执行fetchData
}
/*
也可以使用 takeLatest
不允许并发,dispatch 一个 `USER_FETCH_REQUESTED` action 时,
如果在这之前已经有一个 `USER_FETCH_REQUESTED` action 在处理中,
那么处理中的 action 会被取消,只会执行当前的
*/
function* mySaga() {
yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
}
export default mySaga;
call apply cps
call apply 处理 Promise 的函数和 Generator 函数,会阻塞后面, cps 适合处理Node callback 风格的函数
yield call([obj, obj.method], arg1, arg2, ...) // 如同 obj.method(arg1, arg2 ...)
yield apply(obj, obj.method, [arg1, arg2, ...])
//下面是node 回掉形式处理异步, 在saga 写法
// readFile('/path/to/file', (error, result) => ())
import { cps } from 'redux-saga'
const content = yield cps(readFile, '/path/to/file')
// effects 将会同时执行, 同Promise.all 行为
const [users, repos] = yield [
call(fetch, '/users'),
call(fetch, '/repos')
]
put
redux-saga 为此提供了另外一个函数 put
,这个函数用于创建 dispatch Effect。声明式的解决方案。只需创建一个对象来指示 middleware 我们需要发起一些 action,然后让 middleware 执行真实的 dispatch。 好处对于测试 只需检查 yield 后的 Effect,并确保它包含正确的指令
dispatch({ type: 'PRODUCTS_RECEIVED', products })
yield put({ type: 'PRODUCTS_RECEIVED', products })
take
它创建另一个命令对象,告诉 middleware 等待一个特定的 action, 直到匹配的action 被发起
import { take, put } from 'redux-saga/effects'
// #并在用户初次创建完三条 Todo 信息时显示祝贺信息
function* watchFirstThreeTodosCreation() {
for (let i = 0; i < 3; i++) {
const action = yield take('TODO_CREATED')
}
yield put({type: 'SHOW_CONGRATULATION'})
}
// 同一个地方写控制流
// login 后跟着 logout 监听, logout 后也始终跟着login 监听
function* loginFlow() {
while (true) {
yield take('LOGIN')
// ... perform the login logic
yield take('LOGOUT')
// ... perform the logout logic
}
}
fork
call 会阻塞后面流程, fork不用等待fork的任务结束
const {user, password} = yield take('LOGIN_REQUEST')
//1不用等到fork 任务完成,下面会接着执行,如果监听到 logout
//2 直接取消fork 任务,保持状态统一
//3 fork 返回 Task Object, 所以任务内容全部放在 authorize 里
const task = yield fork(authorize, user, password)
const action = yield take(['LOGOUT', 'LOGIN_ERROR'])
if(action.type === 'LOGOUT')
yield cancel(task) // 不管fork任务有没有完成,直接取消
yield call(Api.clearItem('token'))
}
//4 取消fork 任务后,可能还残留 loginLoading 状态清除,可以在fork
// 异步任务里 try catch finally处 或者 退出登陆时 发送 清除loading
race
在多个 Effects 之间触发一个竞赛
触发一个远程的获取请求,并且限制了 1 秒内响应,否则作超时处理
function* fetchPostsWithTimeout() {
const {posts, timeout} = yield race({
posts: call(fetchApi, '/posts'),
timeout: call(delay, 1000)
})
if (posts)
put({type: 'POSTS_RECEIVED', posts})
else
put({type: 'TIMEOUT_ERROR'})
}
race
的另一个有用的功能,自动取消那些失败的 Effects,假设有 2 个 按钮:
第一个用于在后台启动一个任务,这个任务运行在一个无限循环的 while(true)
中(例如:每 x 秒钟从服务器上同步一些数据)
function* backgroundTask() {
while (true) { ... }
}
function* watchStartBackgroundTask() {
while (true) {
yield take('START_BACKGROUND_TASK')
yield race({
task: call(backgroundTask),
cancel: take('CANCEL_TASK')
})
}
}
//在 CANCEL_TASK action 被发起的情况下,race Effect 将自动取消 backgroundTask,并在 backgroundTask 中抛出一个取消错误。
取消任务
import { take, put, call, fork, cancel, cancelled, delay } from 'redux-saga/effects'
import { someApi, actions } from 'somewhere'
function* bgSync() {
try {
while (true) {
yield put(actions.requestStart())
const result = yield call(someApi)
yield put(actions.requestSuccess(result))
yield delay(5000)
}
} finally {
if (yield cancelled())
yield put(actions.requestFailure('Sync cancelled!'))
}
}
function* main() {
while ( yield take(START_BACKGROUND_SYNC) ) {
// 启动后台任务
const bgSyncTask = yield fork(bgSync)
// 等待用户的停止操作
yield take(STOP_BACKGROUND_SYNC)
// 用户点击了停止,取消后台任务
// 这会导致被 fork 的 bgSync 任务跳进它的 finally 区块
yield cancel(bgSyncTask)
}
}
自动取消
两种情况会自动取消
- 在race 任务中,一个成功其他任务都会被cancel
- 在并行的 Effect (yield […., …]) 中, 其中一个报错,其他的任务都被cancel
yield all[…,…] 和 yield […,…] 不同
Sagas 被实现为 Generator functions,它会 yield 对象到 redux-saga middleware。 被 yield 的对象都是一类指令,指令可被 middleware 解释执行。
当 middleware 取得一个 yield 后的 Promise,middleware 会暂停 Saga,直到 Promise 完成。 在上面的例子中,incrementAsync 这个 Saga 会暂停直到 delay 返回的 Promise 被 resolve,这个 Promise 将在 1 秒后 resolve。
一旦 Promise 被 resolve,middleware 会恢复 Saga 接着执行,直到遇到下一个 yield。 在这个例子中,下一个语句是另一个被 yield 的对象:调用 put({type: ‘INCREMENT’}) 的结果,意思是告诉 middleware 发起一个 INCREMENT 的 action。
put 就是我们称作 Effect 的一个例子。Effects 是一些简单 Javascript 对象,包含了要被 middleware 执行的指令。 当 middleware 拿到一个被 Saga yield 的 Effect,它会暂停 Saga,直到 Effect 执行完成,然后 Saga 会再次被恢复。
put({type: 'INCREMENT'}) // => { PUT: {type: 'INCREMENT'} }
call(delay, 1000) // => { CALL: {fn: delay, args: [1000]}}
middleware 检查每个被 yield 的 Effect 的类型,然后决定如何实现哪个 Effect。如果 Effect 类型是 PUT 那 middleware 会 dispatch 一个 action 到 Store。 如果 Effect 类型是 CALL 那么它会调用给定的函数。