redux saga

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 那么它会调用给定的函数。

Leave a Reply

Your email address will not be published. Required fields are marked *