Redux-сага, веб-сокеты и очередь действий - PullRequest
0 голосов
/ 26 июня 2018

У меня следующая проблема: сервер отправляет сообщения клиенту через websocket. на клиенте мне нужно отобразить это сообщение пользователю. но проблема в том, что иногда сообщения приходят быстро, и мне нужно организовать какую-то очередь и показывать эти сообщения одно за другим.

моя сага:

import { eventChannel, effects, takeEvery } from 'redux-saga';
import { types, actionCreators } from './actions';

const { call, put, take, race } = effects;

function watchMessages(socket) {
  return eventChannel((emitter) => {
    socket.onopen = (e) => (emitter(actionCreators.socketOpen(e)));
    socket.onclose = (e) => (emitter(actionCreators.socketClose(e)));
    socket.onerror = (e) => (emitter(actionCreators.socketError(e)));
    socket.onmessage = (e) => (emitter(actionCreators.socketMessage(e)));

    return () => {
      socket.close();
    };
  });
}

function* internalListener(socket) {
  while (true) {
    const data = yield take(types.SOCKET_SEND);
    socket.send(data.payload);
  }
}

function* externalListener(socketChannel) {
  while (true) {
    const action = yield take(socketChannel);
    yield put(action);
  }
}

function* wsHandling(action) {
  const socket = action.payload.socket;
  while (true) {
    const socketChannel = yield call(watchMessages, socket);
    const { cancel } = yield race({
      task: [call(externalListener, socketChannel), call(internalListener, socket)],
      cancel: take(types.SOCKET_CLOSE),
    });
    if (cancel) {
      socketChannel.close();
    }
  }
}

export default function* rootSaga(action) {
  yield takeEvery(types.SOCKET_CONNECT, wsHandling);
}

мой редуктор:

function dataReducer(state = initialStateData, action) {
  switch (action.type) {
    case types.SOCKET_MESSAGE:
      if (action.payload.channel === 'channel1') {
        return state
          .set('apichannel1', action.payload);
      } else if (action.payload.channel === 'channel2') {
        return state
          .set('apichannel2', action.payload);
      } else if (action.payload.channel === 'channel3') {
        return state
          .set('apichannel3', action.payload);
      }
      return state;
    default:
      return state;
  }
}

так что теперь, когда приходит новое сообщение, я меняю состояние и просто отображаю его на экране.

есть идеи, как я могу превратить это в следующее: помещать поступившие сообщения в какую-то очередь и показывать их одно за другим на экране в течение определенного времени?

Ответы [ 2 ]

0 голосов
/ 03 июля 2018

так я и сделал, вот код, может быть кому-то пригодится

let pendingTasks = [];
let activeTasks = [];

function watchMessages(socket) {
  return eventChannel((emitter) => {
    socket.onopen = (e) => (emitter(actionCreators.socketOpen(e)));
    socket.onclose = (e) => (emitter(actionCreators.socketClose(e)));
    socket.onerror = (e) => (emitter(actionCreators.socketError(e)));
    socket.onmessage = (e) => (emitter(actionCreators.socketMessage(e)));

    return () => {
      socket.close();
    };
  });
}

function* internalListener(socket) {
  while (true) {
    const data = yield take(types.SOCKET_SEND);
    socket.send(data.payload);
  }
}

function* externalListener(socketChannel) {
  while (true) {
    const action = yield take(socketChannel);
    pendingTasks = [...pendingTasks, action];
  }
}

function* wsHandling(action) {
  const socket = action.payload.socket;
  while (true) {
    const socketChannel = yield call(watchMessages, socket);
    const { cancel } = yield race({
      task: [call(externalListener, socketChannel), call(internalListener, socket)],
      cancel: take(types.SOCKET_CLOSE),
    });
    if (cancel) {
      socketChannel.close();
    }
  }
}

function* tasksScheduler() {
  while (true) {
    const canDisplayTask = activeTasks.length < 1 && pendingTasks.length > 0;
    if (canDisplayTask) {
      const [firstTask, ...remainingTasks] = pendingTasks;
      pendingTasks = remainingTasks;
      yield fork(displayTask, firstTask);
      yield call(delay, 300);
    }
    else {
      yield call(delay, 50);
    }
  }
}

function* displayTask(task) {
  activeTasks = [...activeTasks, task];
  yield put(task);
  yield call(delay, 3000);
  activeTasks = _.without(activeTasks, task);
}

export default function* rootSaga(action) {
  yield [
    takeEvery(types.SOCKET_CONNECT, wsHandling),
    takeEvery(types.SOCKET_CONNECT, tasksScheduler),
  ];
}
0 голосов
/ 27 июня 2018

Вы можете буферизовать (очередь) действия в саге redux, используя эффект actionChannel.

https://redux -saga.js.org / Docs / продвинутый / Channels.html # с использованием-The-actionchannel-эффект

Затем вы можете читать из буфера канала со своей скоростью.

Я создал упрощенный пример, который прослушивает сообщения и отображает максимум 3 за раз, каждое по 5 секунд. См:

https://codesandbox.io/s/88m88z2o60

Внизу находится панель консоли, используйте ее для вызова addMsg('Msg: ' + Math.random()) для имитации нового сообщения socket.io.

...