使用redux-saga中間件處理redux中的異步

本文轉(zhuǎn)自我的博客閱讀原文。

整體感知

使用redux-saga封裝異步操作

import { call, put } from 'redux-saga/effects'
import { takeEvery } from 'redux-saga'
// 這個函數(shù)就封裝了我們的異步操作,每一個yield都要等上一個yield完成之后再執(zhí)行
function* fetchData(action) {
   try {
     // 這里其實是用聲明式的方式調(diào)用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的調(diào)用,然后調(diào)用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ā),就一定會調(diào)用fetchData。不管上一次的fetchData有沒有完成
  yield* takeEvery('FETCH_REQUESTED', fetchData)
  // 可以作為節(jié)流函數(shù)使用,還可以避免上一次輸入已經(jīng)清空,但結(jié)果還是返回了上次的輸入得到的結(jié)果
  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í)行某些操作(調(diào)用某些異步函數(shù),發(fā)起一個 action 到 store)。

call

為什么我們使用call聲明式地調(diào)用一個函數(shù)而不是用“函數(shù)()”的方式?這是為了方便我們做斷言測試。
call 同樣支持調(diào)用對象方法,你可以使用以下形式,為調(diào)用的函數(shù)提供一個 this 上下文:

yield call([obj, obj.method], arg1, arg2, ...) // 如同 obj.method(arg1, arg2 ...)

apply

apply 提供了另外一種調(diào)用的方式:

yield apply(obj, obj.method, [arg1, arg2, ...])

cps

call 和 apply 非常適合返回 Promise 結(jié)果的函數(shù)。另外一個函數(shù) cps 可以用來處理 Node 風(fēng)格的函數(shù) (例如,fn(...args, callback) 中的 callback 是 (error, result) => () 這樣的形式,cps 表示的是延續(xù)傳遞風(fēng)格(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 })這樣聲明式的調(diào)用以便測試。

錯誤處理

我們可以使用熟悉的 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 服務(wù)返回一個正常的含有錯誤標識的值。例如, 你可以捕捉 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進行細微調(diào)控。比如說,我們可以在觀察到用戶完成了3個任務(wù)之后,派發(fā)一個向用戶表示祝賀的action。

同時執(zhí)行多個任務(wù)

yield 指令可以很簡單的將異步控制流以同步的寫法表現(xiàn)出來,但與此同時我們將也會需要同時執(zhí)行多個任務(wù),我們不能直接這樣寫:

// 錯誤寫法,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 的行為)。

同時啟動多個任務(wù),擇其優(yōu)者取值

有時候我們同時啟動多個任務(wù),但又不想等待所有任務(wù)完成,我們只希望拿到 勝利者:即第一個被 resolve(或 reject)的任務(wù)。 race Effect 提供了一個方法,在多個 Effects 之間觸發(fā)一個競賽(race)。
下面的示例演示了觸發(fā)一個遠程的獲取請求,并且限制了 1 秒內(nèi)響應(yīng),否則作超時處理。

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。具體實例參看官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容