I have a saga that creates a nesting of setTimeout to post messages via axios like this (for this I call a non generator function in my saga):
import { call, put, takeLatest } from "redux-saga/effects";
import * as Action from "./action";
import axios from "axios";
import { SEND_MESSAGES } from "./constant";
function setTimeoutMessage(messageInfo) {
let index = 0;
setTimeout(function(){tick(index, messageInfo)}, messageInfo[index].delay);
}
function tick(index, messageInfo) {
// send message to server
axios.post("http://127.0.0.1:3000/send", null, {
params: {
ip: messageInfo[index].ip,
port: messageInfo[index].port,
message: messageInfo[index].message
}
})
++index;
if (index < messageInfo.length) {
setTimeout(function(){tick(index, messageInfo)}, messageInfo[index].delay);
}
}
function* sendMessages(action) {
try {
// send message to server
yield call(setTimeoutMessage, action.messageInfos)
yield put(Action.sendMessageSuccess());
yield put(Action.setIsRunning(false));
console.log("end try") //I've also try with 'yield console.log("end try")'
} catch (error) {
yield put(Action.sendMessageFailed(error));
yield put(Action.setIsRunning(false));
console.log("end catch") //also try with 'yield console.log("end catch")'
}
}
function* defaultSaga() {
yield takeLatest(SEND_MESSAGES, sendMessages);
}
export default defaultSaga;
At runtime, the saga executes the yield call(setTimeoutMessage, action.messageInfos)
but before the setTimeout of the non generator function executes, the following actions of the saga are executed (console.log()
and yield put
).
To solve this problem I tried to implement yield race
as below, but I get the same result as before, and the constants assigned to the race are all undefined (even when I’m sure to dispatch the CANCEL
action)
import { call, put, takeLatest, race, take } from "redux-saga/effects";
import * as Action from "./action";
import axios from "axios";
import { SEND_MESSAGES, CANCEL } from "./constant";
function setTimeoutMessage(messageInfo) {
console.log('enter non generator function')
let index = 0;
setTimeout(function(){tick(index, messageInfo)}, messageInfo[index].delay);
}
function tick(index, messageInfo) {
console.log('timeout stuff')
// send message to server
axios.post("http://127.0.0.1:3000/send", null, {
params: {
ip: messageInfo[index].ip,
port: messageInfo[index].port,
message: messageInfo[index].message
}
})
++index;
if (index < messageInfo.length) {
setTimeout(function(){tick(index, messageInfo)}, messageInfo[index].delay);
}
}
function* sendMessages(action) {
const { task, cancel } = yield race({
task: call(setTimeoutMessage, action.messageInfos),
cancel: take(CANCEL)
})
console.log("after race", task, cancel)
if (cancel) {
let id = window.setTimeout(function() {}, 0);
while (id--) {
window.clearTimeout(id);
}
} else if (task) {
yield put(Action.sendMessageSuccess());
} else {
console.log("in if condition nothing ")
yield put(Action.sendMessageFailed());
}
console.log("after yield race + check what effect succeed ")
yield put(Action.setIsRunning(false));
}
function* defaultSaga() {
yield takeLatest(SEND_MESSAGES, sendMessages);
}
export default defaultSaga;
This second implementation gives me the following result in the console:
enter non generator function
after race undefined undefined
in if condition nothing
timeout stuff
The console.log("after yield race + check what effect succeed ")
at the end of my saga function is never displayed
I get the same result if I let my task happen or if I dispatch the CANCEL
action from the beginning
What causes this behavior? How do you force the saga to execute the yield call
before executing the rest of the instructions in the saga?
PS: I don’t use redux-toolkit because I add a feature to an existing code using a relatively old version of redux
You're not yield or return any promise, that generator should wait for. Refactor your function to a saga, and use the delay
saga effect to wait for the next tick:
const postApiMessage = params =>
axios.post("http://127.0.0.1:3000/send", null, { params });
function* setTimeoutMessage(messageInfo) {
let index = 0;
while (index < messageInfo.length) {
const { delay: timeout, ip, port, message } = messageInfo[index];
yield call(postApiMessage, { ip, port, message });
index++;
yield delay(timeout);
}
}
Usage:
function* sendMessages(action) {
try {
// send message to server
yield setTimeoutMessage(action.messageInfos);
yield put(Action.sendMessageSuccess());
yield put(Action.setIsRunning(false));
console.log("end try") //I've also try with 'yield console.log("end try")'
} catch (error) {
yield put(Action.sendMessageFailed(error));
yield put(Action.setIsRunning(false));
console.log("end catch") //also try with 'yield console.log("end catch")'
}
}