React15 - redux-saga 如何在saga中实现轮询接口调用?
在 Redux Saga 中实现轮询接口调用有多种方式,具体取决于你希望的控制粒度(如是否需要手动停止、错误重试策略等)。以下是几种常见的实现模式。
1. 简单循环 + delay
最直接的方式:在 while 循环中使用 delay 控制间隔,每次循环调用 API。
import { call, put, delay } from "redux-saga/effects";
function* pollData() {
while (true) {
try {
const data = yield call(api.fetchData);
yield put({ type: "FETCH_SUCCESS", payload: data });
} catch (error) {
yield put({ type: "FETCH_FAILURE", error });
// 可选的错误处理:比如失败后停止轮询或延长间隔
// break; // 停止轮询
}
yield delay(5000); // 等待 5 秒后再进行下一次请求
}
}
特点:
- 简单直观,启动后会一直运行,直到 Saga 被取消或取消循环(如
break)。 - 无法从外部灵活停止(除非取消整个 Saga)。
2. 可停止的轮询(使用 take 监听停止动作)
如果你需要外部动作(如用户退出页面、点击停止按钮)来停止轮询,可以在循环中加入 race 或 take 来响应停止信号。
import { call, put, delay, take, race } from "redux-saga/effects";
function* pollData() {
while (true) {
const { response, timeout } = yield race({
response: call(api.fetchData),
timeout: delay(5000), // 5秒间隔
});
if (response) {
yield put({ type: "FETCH_SUCCESS", payload: response });
} else {
// 超时后继续等待下一次轮询(其实相当于轮询间隔到了)
}
// 在每次循环末尾检查是否有停止信号
const stopAction = yield take("STOP_POLLING");
if (stopAction) break;
}
}
但上述写法有问题:take 会阻塞循环,导致轮询停止。更好的方式是同时等待两个条件:轮询间隔和停止信号。可以使用 race 来同时等待延迟和停止动作。
import { call, put, delay, take, race } from "redux-saga/effects";
function* pollData() {
while (true) {
// 同时等待 API 调用结果、间隔延迟、停止信号
const { result, stop } = yield race({
result: call(api.fetchData),
stop: take("STOP_POLLING"),
});
if (stop) {
// 收到停止信号,退出循环
break;
}
if (result) {
yield put({ type: "FETCH_SUCCESS", payload: result });
}
// 无论 API 调用成功或失败,都等待下一个间隔
// 可以使用 delay 来控制轮询周期,但注意上面已经用 race 处理了 stop,
// 所以这里再用 delay 会导致 stop 被阻塞。因此更清晰的做法是:
// 在每次循环开头就启动一个 delay,与 API 调用和 stop 并行。
}
}
一个更优雅的结构是使用 fork + cancel 来管理可停止的轮询(见下文)。
3. 使用 fork 创建可取消的轮询任务
将轮询逻辑放在一个独立的子 Saga 中,主 Saga 可以通过 fork 启动它,并在需要时 cancel。
import { call, put, delay, fork, cancel, take } from "redux-saga/effects";
// 轮询子任务
function* pollTask() {
while (true) {
try {
const data = yield call(api.fetchData);
yield put({ type: "FETCH_SUCCESS", payload: data });
} catch (error) {
yield put({ type: "FETCH_FAILURE", error });
// 可选:发生错误时停止轮询或继续
}
yield delay(5000); // 间隔 5 秒
}
}
// 主 Saga:启动轮询,并监听停止信号
function* mainSaga() {
const poller = yield fork(pollTask);
// 等待停止动作
yield take("STOP_POLLING");
yield cancel(poller);
}
优点:
- 轮询逻辑清晰分离。
- 可随时取消,支持多个轮询任务并行。
- 取消后子任务中的
finally块可以执行清理逻辑。
如果需要支持“暂停/恢复”,可以在 pollTask 内部加入 take 监听暂停信号,或者用更复杂的控制流。
4. 带有指数退避的错误重试
轮询时通常需要在失败后延长间隔(指数退避),避免频繁重试。
import { call, put, delay } from "redux-saga/effects";
function* pollWithBackoff() {
let retryDelay = 1000; // 初始延迟 1 秒
const maxDelay = 60000; // 最大延迟 60 秒
while (true) {
try {
const data = yield call(api.fetchData);
yield put({ type: "FETCH_SUCCESS", payload: data });
retryDelay = 1000; // 成功后重置延迟
} catch (error) {
yield put({ type: "FETCH_FAILURE", error });
// 失败后指数退避,但不超过最大延迟
retryDelay = Math.min(retryDelay * 2, maxDelay);
}
yield delay(retryDelay);
}
}
5. 使用 eventChannel 包装轮询(高级)
如果轮询需要与其他事件源结合,或者希望将轮询抽象为流,可以使用 eventChannel。
import { eventChannel, END } from "redux-saga";
import { call, put, take, cancelled } from "redux-saga/effects";
function createPollingChannel(interval) {
return eventChannel((emitter) => {
const timer = setInterval(async () => {
try {
const data = await api.fetchData();
emitter({ type: "DATA", payload: data });
} catch (error) {
emitter({ type: "ERROR", error });
}
}, interval);
// 取消函数
return () => {
clearInterval(timer);
emitter(END);
};
});
}
function* pollWithChannel() {
const channel = yield call(createPollingChannel, 5000);
try {
while (true) {
const action = yield take(channel);
yield put(action); // 将 channel 发出的动作直接 put 到 store
}
} finally {
if (yield cancelled()) {
channel.close();
}
}
}
这种方式更灵活,但复杂度较高,适用于需要将轮询与 Redux 以外的系统集成时。
总结与选择建议
| 需求 | 推荐方式 |
|---|---|
| 简单的固定间隔轮询,无需外部停止 | 方式1:while + delay |
| 需要手动停止(例如用户退出) | 方式3:fork + cancel |
| 需要在轮询中处理暂停/恢复 | 可在方式3的子任务中加入 take 监听 |
| 需要智能重试(指数退避) | 方式4:错误重试逻辑 |
| 轮询逻辑需要与复杂事件流结合 | 方式5:eventChannel |
通常,方式3(fork + cancel) 是最实用且可扩展的方案,它既保持了轮询的独立性,又提供了灵活的外部控制。

浙公网安备 33010602011771号