如何等待 redux-saga 中的另一个动作
How to wait for another action in redux-saga
我有一些故事可能会完成,然后 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()
函数中添加一个循环,但这感觉很糟糕。我对 Javascript、Redux、Saga 或生成器函数不是很熟悉,所以也许这种感觉是完全错误的。我也不确定如何 运行 带有 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;
}
}
saga 中是否有针对此的 API 或函数?这是一个正确的模式,还是我的想法是阻止一个动作直到另一个动作完成错误开始?
从更耦合但更简单的解决方案开始 - 代替使用延迟在循环中等待,您可以使用 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 });
}
解决这个问题的另一种方法是包装获取方法:
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
之类的抓取操作 - 例如显示加载直到您获取令牌,然后才允许应用程序的其余部分请求其他数据。
在这种情况下,您可以修改 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
saga 本身,并且也就是说 buffer the other actions using actionChannel
effect,直到你有令牌 - 这可能会变得有点复杂,因为你需要考虑缓冲什么时候:
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);
}
}
我有一些故事可能会完成,然后 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()
函数中添加一个循环,但这感觉很糟糕。我对 Javascript、Redux、Saga 或生成器函数不是很熟悉,所以也许这种感觉是完全错误的。我也不确定如何 运行 带有 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;
}
}
saga 中是否有针对此的 API 或函数?这是一个正确的模式,还是我的想法是阻止一个动作直到另一个动作完成错误开始?
从更耦合但更简单的解决方案开始 - 代替使用延迟在循环中等待,您可以使用 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 });
}
解决这个问题的另一种方法是包装获取方法:
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
之类的抓取操作 - 例如显示加载直到您获取令牌,然后才允许应用程序的其余部分请求其他数据。
在这种情况下,您可以修改 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
saga 本身,并且也就是说 buffer the other actions using actionChannel
effect,直到你有令牌 - 这可能会变得有点复杂,因为你需要考虑缓冲什么时候:
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);
}
}