本文轉自我的博客閱讀原文荠卷。
整體感知
使用redux-saga封裝異步操作
import { call, put } from 'redux-saga/effects'
import { takeEvery } from 'redux-saga'
// 這個函數(shù)就封裝了我們的異步操作井佑,每一個yield都要等上一個yield完成之后再執(zhí)行
function* fetchData(action) {
try {
// 這里其實是用聲明式的方式調用Api.fetchUser方法堡僻,并傳入?yún)?shù)
const data = yield call(Api.fetchUser, action.payload.url);
yield put({type: "FETCH_SUCCEEDED", data});
} catch (error) {
yield put({type: "FETCH_FAILED", error});
}
}
// 監(jiān)聽INCREMENT_ASYNC action的調用蒋情,然后調用fetchData這個封裝了異步的generate函數(shù)
export function* watchFetchData() {
yield* takeEvery('FETCH_REQUESTED', fetchData)
}
將watchFetchData
這個Saga連接至Store
import { fetchData, watchFetchData } from './sagas'
const store = createStore(
reducer,
applyMiddleware(createSagaMiddleware(watchFetchData))
)
細節(jié)展示
Saga輔助函數(shù)
takeEvery
是最常見的适滓,它提供了類似redux-thunk
的行為仆邓;takeLatest
則只執(zhí)行最后一次fetchData
import { takeEvery } from 'redux-saga'
function* watchFetchData() {
// 只要監(jiān)聽到FETCH_REQUESTED這個action被派發(fā)暑始,就一定會調用fetchData廓握。不管上一次的fetchData有沒有完成
yield* takeEvery('FETCH_REQUESTED', fetchData)
// 可以作為節(jié)流函數(shù)使用搅窿,還可以避免上一次輸入已經清空,但結果還是返回了上次的輸入得到的結果
yield* takeLatest('FETCH_REQUESTED', fetchData)
}
聲明式Effects
在 redux-saga 的世界里隙券,Sagas 都用 Generator 函數(shù)實現(xiàn)男应。我們從 Generator 里 yield 純 JavaScript 對象以表達 Saga 邏輯。 我們稱呼那些對象為 Effect娱仔。Effect 是一個簡單的對象沐飘,這個對象包含了一些給 middleware 解釋執(zhí)行的信息。 你可以把 Effect 看作是發(fā)送給 middleware 的指令以執(zhí)行某些操作(調用某些異步函數(shù)牲迫,發(fā)起一個 action 到 store)耐朴。
call
為什么我們使用call聲明式地調用一個函數(shù)而不是用“函數(shù)()”的方式?這是為了方便我們做斷言測試盹憎。
call 同樣支持調用對象方法筛峭,你可以使用以下形式,為調用的函數(shù)提供一個 this 上下文:
yield call([obj, obj.method], arg1, arg2, ...) // 如同 obj.method(arg1, arg2 ...)
apply
apply 提供了另外一種調用的方式:
yield apply(obj, obj.method, [arg1, arg2, ...])
cps
call 和 apply 非常適合返回 Promise 結果的函數(shù)脚乡。另外一個函數(shù) cps 可以用來處理 Node 風格的函數(shù) (例如蜒滩,fn(...args, callback) 中的 callback 是 (error, result) => () 這樣的形式滨达,cps 表示的是延續(xù)傳遞風格(Continuation Passing Style))。
import { cps } from 'redux-saga'
const content = yield cps(readFile, '/path/to/file')
put
同樣的俯艰,如果我們想在獲取到異步數(shù)據(jù)之后派發(fā)一個action也不能直接dispatch({ type: 'PRODUCTS_RECEIVED', products })
而是要用yield put({ type: 'PRODUCTS_RECEIVED', products })
這樣聲明式的調用以便測試捡遍。
錯誤處理
我們可以使用熟悉的 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 })
}
}
當然了竹握,你并不一定得在 try/catch 區(qū)塊中處理錯誤画株,你也可以讓你的 API 服務返回一個正常的含有錯誤標識的值。例如啦辐, 你可以捕捉 Promise 的拒絕操作谓传,并將它們映射到一個錯誤字段對象。
import Api from './path/to/api'
import { take, put } from 'redux-saga/effects'
function fetchProductsApi() {
return Api.fetch('/products')
.then(response => {response})
.catch(error => {error})
}
function* fetchProducts() {
const { response, error } = yield call(fetchProductsApi)
if(response)
yield put({ type: 'PRODUCTS_RECEIVED', products: response })
else
yield put({ type: 'PRODUCTS_REQUEST_FAILED', error })
}
高級(官方中文文檔)
監(jiān)聽未來的action
之前我們有用takeEvery
來對每一個相同的action進行無差別對待芹关,但其實我們也有take
可以對每一次的action進行細微調控续挟。比如說,我們可以在觀察到用戶完成了3個任務之后侥衬,派發(fā)一個向用戶表示祝賀的action诗祸。
同時執(zhí)行多個任務
yield 指令可以很簡單的將異步控制流以同步的寫法表現(xiàn)出來,但與此同時我們將也會需要同時執(zhí)行多個任務轴总,我們不能直接這樣寫:
// 錯誤寫法直颅,effects 將按照順序執(zhí)行
const users = yield call(fetch, '/users'),
repos = yield call(fetch, '/repos')
import { call } from 'redux-saga/effects'
// 正確寫法, effects 將會同步執(zhí)行
const [users, repos] = yield [
call(fetch, '/users'),
call(fetch, '/repos')
]
當我們需要 yield 一個包含 effects 的數(shù)組, generator 會被阻塞直到所有的 effects 都執(zhí)行完畢怀樟,或者當一個 effect 被拒絕 (就像 Promise.all 的行為)功偿。
同時啟動多個任務,擇其優(yōu)者取值
有時候我們同時啟動多個任務往堡,但又不想等待所有任務完成械荷,我們只希望拿到 勝利者:即第一個被 resolve(或 reject)的任務。 race Effect 提供了一個方法投蝉,在多個 Effects 之間觸發(fā)一個競賽(race)养葵。
下面的示例演示了觸發(fā)一個遠程的獲取請求征堪,并且限制了 1 秒內響應瘩缆,否則作超時處理。
import { race, take, put } from 'redux-saga/effects'
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庸娱。具體實例參看官方文檔