Как реализовать пакетную задачу в redux-saga с ограниченным параллелизмом и вменяемой отменой? - PullRequest
1 голос
/ 10 июня 2019

Я пытаюсь осуществить загрузку изображений через redux-сагу. Функции, которые мне нужно включить:

  • Лимит одновременных загрузок. Это достигается путем использования channel как , описанного в саговой документации

  • Действие, которое я слушаю, START_UPLOADS в приведенном ниже коде, содержит (возможно, довольно длинный) массив файлов, которые публикуются на канале отдельно.

  • Мне нужно иметь возможность отменить все текущие загрузки с помощью другого действия, CANCEL_ACTION, включая те, которые поступили в каком-либо START_UPLOADS, но еще не были опубликованы на канале, а также те, которые в настоящее время обрабатываются в любом uploadImage рабочих.

Код, который я получил, выглядит следующим образом. Моя проблема с этим заключается в том, что обработчик cancelAll выполняется ПОСЛЕ блока finally, который перезапускает сагу, и общий факт, что мне, кажется, нужно перезапустить все. Это выглядит неуклюжим и подверженным ошибкам. Можете ли вы дать какой-либо совет относительно того, как саги предназначены для использования?

function* uploadImage(file) {
  const config = yield getConfig();
  const getRequest = new SagaRequest();
  console.log("Making async request here.");
}

function* consumeImages(uploadRequestsChannel) {
  while (true) {
    const fileAdded = yield take(uploadRequestsChannel);
    // process the request
    yield* uploadImage(fileAdded);
  }
}

function* uploadImagesSaga() {
  const CONCURRENT_UPLOADS = 10;
  const uploadRequestsChannel = yield call(channel);
  let workers = [];
  function* scheduleWorkers() {
    workers = [];
    for (let i = 0; i < CONCURRENT_UPLOADS; i++) {
      const worker = yield fork(consumeImages, uploadRequestsChannel);
      workers.push(worker);
    }
  }

  let listener;
  yield* scheduleWorkers();

  function* cancelAll() {
    // cancel producer and consumers, flush channel
    yield cancel(listener);
    for (const worker of workers) {
      yield cancel(worker);
    }
    yield flush(uploadRequestsChannel);
  }

  function* putToChannel(chan, task) {
    return yield put(chan, task);
  }

  function* listenToUploads() {
    try {
      while (true) {
        const { filesAdded } = yield take(START_UPLOADS);
        for (const fileAdded of filesAdded) {
          yield fork(putToChannel, uploadRequestsChannel, fileAdded);
        }
      }
    } finally {
      // if cancelled, restart consumers and producer
      yield* scheduleWorkers();
      listener = yield fork(listenToUploads);
    }
  }

  listener = yield fork(listenToUploads);

  while (true) {
    yield take(CANCEL_ACTION);
    yield call(cancelAll);
  }
}

export default uploadImagesSaga;

РЕДАКТИРОВАТЬ: перегонять в песочницу здесь: https://codesandbox.io/s/cancellable-counter-example-qomw6

1 Ответ

2 голосов
/ 11 июня 2019

Мне нравится использовать race для отмены - разрешенное значение расы - это объект с одним ключом и значением (из задачи «выигрыш»). Rasex-Saga Race () документы

const result = yield race({
  cancel: take(CANCEL_ACTION),
  listener: call(listenToUploads), // use blocking `call`, not fork
});

if (result.cancel) {
  yield call(cancelAll)
}

^ Это может быть заключено в цикл while (true), так что вы сможете объединить дублированные функции fork () из исходного примера. Если рабочие должны быть перепланированы, вы можете рассмотреть возможность обработки этого внутри cancelAll.

Я предпочитаю перезапускать дескриптор внешней задачи, а не вызывать задачи из их собственных finally блоков.

Редактировать: пример песочницы с рефакторингом https://codesandbox.io/s/cancellable-counter-example-j5vxr

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...