redux-sage和redux-thunk相似都是redux的中間件,都用於處理異步操做。redux-saga使用ES6的Generator功能,避免了redux-thunk的回調寫法,而且便於測試。redux
下面展現了最簡單是使用示例api
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); } /* 也可使用 takeLatest 不容許併發,dispatch 一個 `USER_FETCH_REQUESTED` action 時, 若是在這以前已經有一個 `USER_FETCH_REQUESTED` action 在處理中, 那麼處理中的 action 會被取消,只會執行當前的 */ function* mySaga() { yield takeLatest("USER_FETCH_REQUESTED", fetchUser); } export default mySaga;
import { createStore, applyMiddleware } from 'redux' import createSagaMiddleware from 'redux-saga' import reducer from './reducers' import mySaga from './sagas' // create the saga middleware const sagaMiddleware = createSagaMiddleware() // mount it on the Store const store = createStore( reducer, applyMiddleware(sagaMiddleware) ) // then run the saga sagaMiddleware.run(mySaga) // render the application
put等一些方法是saga提供的指令,返回一個Effect,Effect是一個簡單的js對象,包含了要被sgag middleware執行的指令,當middleware拿到一個被saga yield的Effect,它會暫停saga,直到Effect執行完成,而後saga回恢復執行。服務器
redux-saga提供一些輔助函數,用於在action被髮起到store是派生人物。併發
takeEvery()能夠在某個action發起是啓動一個任務app
import { takeEvery } from 'redux-saga' function* watchFetchData() { yield* takeEvery('FETCH_REQUESTED', fetchData) }
takeLatest()和takeEvent()功能相似,不一樣的是takeEvent()容許同時啓動多個任務,即便已啓動的任務未結束,也會啓動一個新的任務。takeLatest()同時只容許啓動一個任務,一個新的任務啓動,以前的任務即便未完成也會被取消。異步
若是有多個sage監聽不一樣的action函數
import { takeEvery } from 'redux-saga/effects' // FETCH_USERS function* fetchUsers(action) { ... } // CREATE_USER function* createUser(action) { ... } // 同時使用它們 export default function* rootSaga() { yield takeEvery('FETCH_USERS', fetchUsers) yield takeEvery('CREATE_USER', createUser) }
在 Generator 函數中,yield
右邊的任何表達式都會被求值,結果會被 yield 給調用者。測試
測試時我咱們不可能真正的執行任務發到服務器,咱們須要檢測的只是yield後面語句調用(一般是調用參數的檢查)或值是否正確。但當yield 一個返回值是Promise的方法時,就沒辦法比較了,因此當yield一個異步api時使用saga提供的call()方法代替異步api的直接調用,call()返回一個普通的js對象,因此在測試時能夠很容易檢測。fetch
import { call } from 'redux-saga/effects' function* fetchProducts() { const products = yield call(Api.fetch, '/products') // ... }
import { call } from 'redux-saga/effects' import Api from '...' const iterator = fetchProducts() // expects a call instruction assert.deepEqual( iterator.next().value, call(Api.fetch, '/products'), "fetchProducts should yield an Effect call(Api.fetch, './products')" )
當dispatch一個action時,也一樣難以測試,因此saga提供了put()方法來代替直接調用dispatch。spa
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 })" )
錯誤處理
使用try...catch來處理錯誤
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 }) } }
在測試時使用throw來拋出一個錯誤
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 })" )