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 监听停止动作)

如果你需要外部动作(如用户退出页面、点击停止按钮)来停止轮询,可以在循环中加入 racetake 来响应停止信号。

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 是最实用且可扩展的方案,它既保持了轮询的独立性,又提供了灵活的外部控制。

posted @ 2026-03-26 23:00  箫笛  阅读(1)  评论(0)    收藏  举报