Redux-saga學習筆記javascript
概述java
Redux-saga在Redux應用中扮演’中間件’的角色,主要用來執行數據流中的異步操做。主要經過ES6中的generator函數和yield關鍵字來以同步的方式實現異步操做。redux
基本用法:api
APIpromise
用來監聽action,每一個action都觸發一次,若是其對應是異步操做的話,每次都發起異步請求,而不論上次的請求是否返回。緩存
import { takeEvery } from 'redux-saga/effects' function* watchFetchData() { yield takeEvery('FETCH_REQUESTED', fetchData) }
做用同takeEvery同樣,惟一的區別是它只關注最後,也就是最近一次發起的異步請求,若是上次請求還未返回,則會被取消。app
function* watchFetchData() { yield takeLatest('FETCH_REQUESTED', fetchData) }
call用來調用異步函數,將異步函數和函數參數做爲call函數的參數傳入,返回一個js對象。saga引入他的主要做用是方便測試,同時也能讓咱們的代碼更加規範化。異步
同js原生的call同樣,call函數也能夠指定this對象,只要把this對象當第一個參數傳入call方法就行了函數
saga一樣提供apply函數,做用同call同樣,參數形式同js原生apply方法。 單元測試
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')" ) yield call([obj, obj.method], arg1, arg2, ...) yield apply(obj, obj.method, [arg1, arg2, ...])
同call方法基本同樣,可是用處不太同樣,call通常用來完成異步操做,cps能夠用來完成耗時比較長的io操做等。
put是saga對Redux中dispatch方法的一個封裝,調用put方法後,saga內部會分發action通知Store更新state。
這個藉口主要也是爲了方便咱們寫單元測試提供的。
import { call, put } from 'redux-saga/effects' // ... function* fetchProducts() { const products = yield call(Api.fetch, '/products') // create and yield a dispatch Effect yield put({ type: 'PRODUCTS_RECEIVED', products }) } const products = {} // expects a dispatch instruction 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 }) } } import Api from './path/to/api' import { call, 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 }) }
take的表現同takeEvery同樣,都是監聽某個action,但與takeEvery不一樣的是,他不是每次action觸發的時候都相應,而只是在執行順序執行到take語句時纔會相應action。
當在genetator中使用take語句等待action時,generator被阻塞,等待action被分發,而後繼續往下執行。
takeEvery只是監聽每一個action,而後執行處理函數。對於什麼時候相應action和 如何相應action,takeEvery並無控制權。
而take則不同,咱們能夠在generator函數中決定什麼時候相應一個action,以及一個action被觸發後作什麼操做。
最大區別:take只有在執行流達到時纔會響應對應的action,而takeEvery則一經註冊,都會響應action。
import { take, put } from 'redux-saga/effects' function* watchFirstThreeTodosCreation() { for (let i = 0; i < 3; i++) { const action = yield take('TODO_CREATED') } yield put({type: 'SHOW_CONGRATULATION'}) }
非阻塞任務調用機制:上面咱們介紹過call能夠用來發起異步操做,可是相對於generator函數來講,call操做是阻塞的,只有等promise回來後才能繼續執行,而fork是非阻塞的 ,當調用fork啓動一個任務時,該任務在後臺繼續執行,從而使得咱們的執行流能繼續往下執行而沒必要必定要等待返回。
import { take, call, put, cancelled } from 'redux-saga/effects' import Api from '...' function* authorize(user, password) { try { const token = yield call(Api.authorize, user, password) yield put({type: 'LOGIN_SUCCESS', token}) yield call(Api.storeItem, {token}) return token } catch(error) { yield put({type: 'LOGIN_ERROR', error}) } finally { if (yield cancelled()) { // ... put special cancellation handling code here } } }
cancel的做用是用來取消一個還未返回的fork任務。防止fork的任務等待時間太長或者其餘邏輯錯誤。
all提供了一種並行執行異步請求的方式。以前介紹過執行異步請求的api中,大都是阻塞執行,只有當一個call操做放回後,才能執行下一個call操做, call提供了一種相似Promise中的all操做,能夠將多個異步操做做爲參數參入all函數中,若是有一個call操做失敗或者全部call操做都成功返回,則本次all操做執行完畢。
import { all, call } from 'redux-saga/effects' // correct, effects will get executed in parallel const [users, repos] = yield all([ call(fetch, '/users'), call(fetch, '/repos') ])
有時候當咱們並行的發起多個異步操做時,咱們並不必定須要等待全部操做完成,而只須要有一個操做完成就能夠繼續執行流。這就是race藉口的用處。他能夠並行的啓動多個異步請求,只要有一個 請求返回(resolved或者reject),race操做接受正常返回的請求,而且將剩餘的請求取消。
import { race, take, put } from 'redux-saga/effects' function* backgroundTask() { while (true) { ... } } function* watchStartBackgroundTask() { while (true) { yield take('START_BACKGROUND_TASK') yield race({ task: call(backgroundTask), cancel: take('CANCEL_TASK') }) } }
在以前的操做中,全部的action分發是順序的,可是對action的響應是由異步任務來完成,也便是說對action的處理是無序的。
若是須要對action的有序處理的話,可使用actionChannel函數來建立一個action的緩存隊列,但一個action的任務流程處理完成後,才但是執行下一個任務流。
代碼參考:
import { take, actionChannel, call, ... } from 'redux-saga/effects' function* watchRequests() { // 1- Create a channel for request actions const requestChan = yield actionChannel('REQUEST') while (true) { // 2- take from the channel const {payload} = yield take(requestChan) // 3- Note that we're using a blocking call yield call(handleRequest, payload) } } function* handleRequest(payload) { ... }
用來防止接二連三的響應某個事件。
import { throttle } from 'redux-saga/effects' function* handleInput(input) { // ... } function* watchInput() { yield throttle(500, 'INPUT_CHANGED', handleInput) }
延時執行,使用delay函數實現
import { delay } from 'redux-saga' function* handleInput(input) { // debounce by 500ms yield call(delay, 500) ... } function* watchInput() { let task while (true) { const { input } = yield take('INPUT_CHANGED') if (task) { yield cancel(task) } task = yield fork(handleInput, input) } } const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))
參考:https://redux-saga.js.org/docs/api/