Search code examples
javascriptreduxgeneratorredux-sagayield

Generator function does not pause execution if I call a non generator function inside of it


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


Solution

  • 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")'
      }
    }