I'm trying to implement image uploads via redux-saga. The features I need to include are:
Concurrent uploads limit. This is achieved by using a
channelas described in saga docsThe action I listen to,
START_UPLOADSin the code below, contains a (possible quite long) array of files which are published to the channel separately.I need to be able to cancel all current uploads via another action,
CANCEL_ACTION, including those that arrived in any START_UPLOADS but weren't published to the channel yet, as well as those currently being processed in any of theuploadImageworkers.
The code I arrived at is as follows. My problem with it is that the cancelAll handler is being executed AFTER the finally block that restarts the saga and the overall fact that I seem to need to restart everything. It looks clunky and error prone. Can you give any advice on whether this is how Sagas are meant to be used?
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;
EDIT: distilled into a sandbox here: https://codesandbox.io/s/cancellable-counter-example-qomw6