整理和总结官方文档的中文翻译版上面对于Redux-sage的介绍
文档地址:http://leonshi.com/redux-saga-in-chinese/index.html
概述
redux-saga 是一个用于管理Redux 应用异步操作的中间件(又称异步 action)。 redux-saga 通过创建 Sagas 将所有的异步操作逻辑收集在一个地方集中处理,可以用来代替 redux-thunk中间件。
Sagas 只会在应用启动时调用。 Sagas 可以被看作是在后台运行的进程。Sagas 监听发起的 action,然后决定基于这个 action 来做什么:是发起一个异步调用(比如一个 Ajax 请求),还是发起其 他的 action 到 Store,甚至是调用其他的 Sagas。
在 redux-saga 的世界里,所有的任务都通用 yield Effects 来完成(译注:Effect 可以看作是 redux-saga 的任务单元)。Effects 都是简单的 Javascript 对象,包含了要被 Saga middleware 执行的信息(打个比方,你可以看到 Redux action 其实是一个个包含执行信息的对象)
因为使用了 Generator,redux-saga让你可以用同步的方式写异步代码。
基本概念
1 Saga 辅助函数
takeEvery :允许多个 fetchData实例同时启动。在某个特定时刻,我们可以启动一个新的 fetchData任务, 尽管之前还有一个或多个 fetchData尚未结束。
takeLatest:只允许执行一个 fetchData任务。并且这个任务是最后被启动的那个。 如果之前已经有一个任务在执行,那之前的这个任务会自动被取消。
例子:
import { takeEvery } from 'redux-saga'
function* watchFetchData() {
yield* takeEvery('FETCH_REQUESTED', fetchData)
}```
监听 FETCH_REQUESTED action 之后执行下面的异步action 任务
import { call, put } from 'redux-saga/effects'
export function* fetchData(action) {
try {
const data = yield call(Api.fetchUser, action.payload.url);
yield put({type: "FETCH_SUCCEEDED", data});
} catch (error) {
yield put({type: "FETCH_FAILED", error});
}
}
#####2 声明式 Effects
我们从 Generator 里 yield 纯 JavaScript 对象以表达 Saga 逻辑。 我们称呼那些对象为 *Effect*。Effect 是一个简单的对象,这个对象包含了一些给 middleware 解释执行的信息。 你可以把 Effect 看作是发送给 middleware 的指令以执行某些操作(调用某些异步函数,发起一个 action 到 store)。
一个 Saga 所做的实际上是组合那些所有的 Effect,共同实现所需的控制流。 最简单的是只需把 yield 一个接一个地放置,就可对 yield 过的 Effect 进行排序。
我们已经看到,使用 Effect 诸如 call 和 put,与高阶 API 如 takeEvery相结合,让我们实现与redux-thunk 同样的东西, 但又有额外的易于测试的好处。
######2.1 Effect 概念是如何让 Sagas 很容易地被测试的
假设我们有一个监听 PRODUCTS_REQUESTED action 的Saga。每次匹配到 action,它会启动一个从服务器上获取产品列表的任务。
现在要对这段逻辑编写测试, 在测试过程中,执行真正的服务是一个既不可行也不实用的方法,所以我们必须 模拟函数。也就是说,我们需要将真实的函数替换为一个假的,这个假的函数并不会真的发送 AJAX 请求而只会检查是否使用正确的参数调用方法。模拟使测试更加困难和不可靠。
实际上对这段逻辑的测试我们需要的只是保证任务 yield 一个正确的函数,并且这个函数有着正确的参数。 **我们可以仅仅 yield 一条描述函数调用的信息**
实际中当yield 一个方法时,可以使用call(fn, ...args)这个函数,**call创建了一条描述结果的信息**,call 只是一个返回纯文本对象的函数,redux-saga middleware 确保执行函数调用并在响应被 resolve 时恢复 generator。
代码示例
import { takeEvery } from 'redux-saga'
import Api from './path/to/api'
function* watchFetchProduts() {
yield* takeEvery('PRODUCTS_REQUESTED',fetchProducts)
}
function* fetchProducts() {
const products = yield call(Api.fetch, '/products')
}
//测试代码
import { call } from 'redux-saga/effects'
import Api from '...'
const iterator = fetchProducts()
assert.deepEqual(
iterator.next().value, call(Api.fetch, '/products')
)
#####3 发起 action 到 store
我们想发起一些action通知
dispatch({type:'PRODUCTS_RECEIVED', products })
为了编写方便测试的代码,使用Effect的概念,使用指令的方式来发起通话,**只需创建一个对象来指示 middleware 我们需要发起一些 action,然后让 middleware 执行真实的 dispatch。**这种方式我们就可以同样的方式测试 Generator 的 dispatch:只需检查 yield 后的 Effect,并确保它包含正确的指令。指令为put
代码示例
import { call, put } from 'redux-saga/effects'
//...
function* fetchProducts() {
const products = yield call(Api.fetch, '/products')
// 创建并 yield 一个 dispatch Effect
yield put({ type: 'PRODUCTS_RECEIVED', products })
}
//测试代码
import { call, put } from 'redux-saga/effects'
import Api from '...'
const iterator = fetchProducts()
// 期望一个 call 指令
assert.deepEqual(
iterator.next().value, call(Api.fetch, '/products'),
"fetchProducts should yield an Effect call(Api.fetch,'./products')"
)
// 创建一个假的响应对象
const products = {}
// 期望一个 dispatch 指令
assert.deepEqual(
iterator.next(products).value, put({ type:'PRODUCTS_RECEIVED', products }),
"fetchProducts should yield an Effect put({ type: 'PRODUCTS_RECEIVED', products })"
)
#####4 错误处理
我们可以使用熟悉的 try/catch语法在 Saga 中捕获错误。
代码示例
import Api from './path/to/api'
import { call, put } from 'redux-saga/effects'
//...
function* fetchProducts() {
try {
const products = yield call(Api.fetch, '/products')
yield put({ type: 'PRODUCTS_RECEIVED', products })
} catch(error) {
yield put({ type: 'PRODUCTS_REQUEST_FAILED', error })
}
}
//测试代码
import { call, put } from 'redux-saga/effects'
import Api from '...'
const iterator = fetchProducts()
// 期望一个 call 指令
assert.deepEqual(
iterator.next().value, call(Api.fetch, '/products'), "fetchProducts should yield an Effect call(Api.fetch, './products')"
)
// 创建一个模拟的 error 对象
const error = {}
// 期望一个 dispatch 指令
assert.deepEqual(
iterator.throw(error).value, put({ type: 'PRODUCTS_REQUEST_FAILED', error }),
"fetchProducts should yield an Effect put({ type: 'PRODUCTS_REQUEST_FAILED', error })"
)
//官网还提供了一种捕捉 Promise 的拒绝操作,并将它们映射到一个错误字段对象,可以查阅。个人喜欢第一种错误处理方式