簡體   English   中英

如何在 redux-saga 中等待另一個動作

[英]How to wait for another action in redux-saga

我有一些 sagas 可能會完成,然后put另一個動作放入商店。

有些 sagas 應該只在其他 sagas 執行后才執行:它們必須阻塞,或者等到另一個 sagas 完成。

總結如下:

export function* authorize(action) {
  const { clientId } = action.data;

  const response = yield call(apiAuthorize, clientId);
  // Redux reducer picks this up and sets a token in storage. 
  yield put({ type: AUTHORIZE_SUCCEEDED, data: response.data.data });
}

export function* fetchMessages(action) {
  console.log(action);
  const { timelineId } = action.data;

  // how can we block this until either `token` is set (getToken returns non-null)
  //  or until AUTHORIZE_SUCCEEDED is sent?

  // The token set by AUTHORIZED_SUCCEEDED is read from the storage.
  // This will be null untill the AUTHORIZE_SUCCEEDED is handled by redux.
  // When null, the api-call will return a 401 so we want to block  untill we
  // have the token.
  const token = yield select(getToken);
  const response = yield call(apiFetchMessages, token);
  yield put({ type: MESSAGES_REQUEST_SUCCEEDED, data: response.data.data });
}

export default function* appSaga() {
  yield takeEvery(AUTHORIZE_REQUESTED, authorize);
  yield takeEvery(MESSAGES_REQUESTED, fetchMessages);
}

我試圖在 sagas 之間保持盡可能少的耦合,因此向我展示了一種在函數之外實現這一點的方法的獎勵積分。

請注意,這是一個簡化版本。 實際上,有幾個這樣的fetchMessages可能會被觸發,所有這些都應該等到 AUTHORIZE_SUCCEEDED 進來。

我可以在fetchMessage() function 中添加一個循環,但這感覺很惡心。 我對Javascript、Redux、Saga或者generator函數不是很熟悉,所以也許這種感覺是完全錯誤的。 我也不確定如何使用 sagas 的 yield/select 等超時運行循環。

while (true) {
  const token = yield setTimeout(() => select(getToken), 1000);
  if (!!token) { 
    break;
  }
});

另一個有效但笨拙的技巧是在 401 上重試 fetchMessages api 調用。

try {
  const response = yield call(apiFetchMessages, token);
  yield put({ type: MESSAGES_REQUEST_SUCCEEDED, data: response.data.data });
} catch (error) {
  if (error.request.status === 401) {
    yield put({ type: MESSAGES_REQUESTED, data: { blockId } });
  } else {
    throw error;
  }
}

傳奇中是否有 API 或 function ? 這是一個正確的模式,還是我阻止一個動作直到另一個動作完成的想法一開始是錯誤的?

從更耦合,但更簡單的解決方案開始——而不是在循環中使用延遲等待,您可以使用take來等待AUTHORIZE_SUCCEEDED操作:

export function* fetchMessages(action) {
  const { timelineId } = action.data;

  // the cycle might not be needed if you are sure the 
  // AUTHORIZE_SUCCEEDED action is always dispatched with a valid token
  let token;
  while (true) {
     token = yield select(getToken);
     if (token) break;
     yield take(AUTHORIZE_SUCCEEDED);
  }

  const response = yield call(apiFetchMessages, token);
  yield put({ type: MESSAGES_REQUEST_SUCCEEDED, data: response.data.data });
}

為了讓它不那么笨重,你可以把它抽象成它自己的傳奇:

export function* getTokenSaga() {
  let token;
  while (true) {
     token = yield select(getToken);
     if (token) break;
     yield take(AUTHORIZE_SUCCEEDED);
  }
  return token;
}

export function* fetchMessages(action) {
  const { timelineId } = action.data;

  const token = yield call(getTokenSaga);
  const response = yield call(apiFetchMessages, token);
  yield put({ type: MESSAGES_REQUEST_SUCCEEDED, data: response.data.data });
}

另一種解決方法是包裝 fetching 方法:

export function* fetchWithToken(fetchFn, ...params) {
  let token;
  while (true) {
     token = yield select(getToken);
     if (token) break;
     yield take(AUTHORIZE_SUCCEEDED);
  }
  return yield call(fetchFn, token, ...params);
}

export function* fetchMessages(action) {
  const { timelineId } = action.data;

  const response = yield call(fetchWithToken, apiFetchMessages);
  yield put({ type: MESSAGES_REQUEST_SUCCEEDED, data: response.data.data });
}

可能解決此問題的完全不同的方法是更改您的應用程序的架構,以確保在您獲得令牌之前不會調度諸如MESSAGES_REQUESTED之類的獲取操作 - 例如,在您獲得令牌之前顯示加載,然后才允許 rest請求附加數據的應用程序。

在這種情況下,您可以修改fetch方法本身以獲取令牌,因為它始終可用:

const loadData = (endpoint, payload) => {
  const token = getTokenSelector(store.getState())
  return fetch(endpoint, payload).then(...);
}

const apiFetchMessages = () => {
  return loadData('/messages');
}

export function* fetchMessages(action) {
  const { timelineId } = action.data;

  const response = yield call(apiFetchMessages);
  yield put({ type: MESSAGES_REQUEST_SUCCEEDED, data: response.data.data });
}

如果在您分派動作的地方不可能進行這樣的更改,那么我還有另一種方法可以考慮如何確保令牌始終可用而不修改fetchMessages傳奇本身,那就是使用緩沖其他動作actionChannel效果,直到您擁有令牌 - 這可能會變得更加復雜,因為您需要考慮在以下情況下要緩沖什么:

export default function* appSaga() {
  // we buffer all fetching actions
  const channel = yield actionChannel([MESSAGES_REQUESTED, FOO_REQUESTED]);

  // then we block the saga until AUTHORIZE_REQUESTED is dispatched and processed
  const action = yield take(AUTHORIZE_REQUESTED);
  yield call(authorize, action);

  // There is multiple ways to process the buffer, for example
  // we can simply redispatch the actions once we started
  // listening for them using the `takeEvery` effect
  yield takeEvery(MESSAGES_REQUESTED, fetchMessages);
  yield takeEvery(FOO_REQUESTED, fetchFoo);
  while (const action = yield take(channel)) {
    yield put(action);
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM