Search code examples
javascriptecmascript-6reduxes6-promiseredux-saga

Unable to get Redux put to work within generator function


Edit 1

So I have now updated the projectorSaga function to use yield call() as suggested, but I now get the following error every time I call the function, which I think is due to the way I have nested the functions and returning promises not functions that return promises, but I'm at a bit of a loss as to how to restructure this to fix it.

redux-saga error: uncaught at check
call: argument [object Promise] is not a function
[14:57:43.351] [verbose] Error: call: argument [object Promise] is not a function

Here's the update saga code, other functions are still as below.

import { put, takeEvery, call } from 'redux-saga/effects';

import * as pjControl from '../../pjcontrol/pjControl';

import { TURNON, TURNOFF, SENDCOMMAND, SHUTTERCLOSE, SHUTTEROPEN } from '../actions/actionTypes';

const log = require('electron-log');

const ip = '192.168.100.100';
const port = 3629;
const model = 'L1500';

function* sendCommand(payload) {
  yield put({ type: 'SENDING COMMAND', payload });
  try {
    let response;
    const command = payload.payload.command;
    if (command === TURNON) {
      response = yield call(pjControl.turnOn(ip, port, model));
    } else if (command === TURNOFF) {
      response = yield call(pjControl.turnOff(ip, port, model));
    } else if (command === SHUTTERCLOSE) {
      response = yield call(pjControl.shutterClose(ip, port, model));
    } else if (command === SHUTTEROPEN) {
      response = yield call(pjControl.shutterOpen(ip, port, model));
    } else {
      throw Error('Unknwon Command');
    }
    log.verbose(response);
    yield put({ type: 'SEND_COMMAND_SUCCEEDED', response });
  } catch (e) {
    log.verbose(e);
    yield put({ type: 'SEND_COMMAND_FAILED', e });
  }
}

export default function* projectorSetSaga() {
  yield takeEvery(SENDCOMMAND, sendCommand);
}

Original

I've just started trying to use Redux-Saga in an Electron application I've been working on, and I am having issues getting a put to work in one of my generator functions. I suspect it's me not understanding the nesting of say functions and callback e.t.c, but I've tried this in many many ways so far and no difference.

The oddness of the issue is that the initial yield in sendCommand does cause an event to appear in Redux. Also the log commands I have in place do get called when I would expect them to, with the correct values. And if the connection times out, the log in the catch get's called, but neither of the throw commands ever appear in Redux.

This is my root Saga:

import { all, call } from 'redux-saga/effects';

import projectorGetSaga from './projectorGetSaga';
import projectorSetSaga from './projectorSetSaga';

export default function* rootSaga() {
  yield all([
    call(projectorGetSaga),
    call(projectorSetSaga),
  ]);
}

And this is projectorSetSaga, which I'm struggling with.

import { put, takeEvery } from 'redux-saga/effects';

import * as pjControl from '../../pjcontrol/pjControl';

import { TURNON, TURNOFF, SENDCOMMAND, SHUTTERCLOSE, SHUTTEROPEN } from '../actions/actionTypes';

const log = require('electron-log');

const ip = '192.168.100.100';
const port = 3629;
const model = 'L1500';

function* sendCommand(payload) {
  yield put({ type: 'SENDING COMMAND', payload });
  try {
    let response;
    const command = payload.payload.command;
    if (command === TURNON) {
      response = yield pjControl.turnOn(ip, port, model);
    } else if (command === TURNOFF) {
      response = yield pjControl.turnOff(ip, port, model);
    } else if (command === SHUTTERCLOSE) {
      response = yield pjControl.shutterClose(ip, port, model);
    } else if (command === SHUTTEROPEN) {
      response = yield pjControl.shutterOpen(ip, port, model);
    } else {
      throw Error('Unknwon Command');
    }
    log.verbose(response);
    yield put({ type: 'SEND_COMMAND_SUCCEEDED', response });
  } catch (e) {
    log.verbose(e);
    yield put({ type: 'SEND_COMMAND_FAILED', e });
  }
}

export default function* projectorSetSaga() {
  yield takeEvery(SENDCOMMAND, sendCommand);
}

Here is the rest of the code for the functions the saga is calling as it might be relevant, I may have completely misunderstood the way the async function/promises e.t.c need to be returned.

The PJ Control function that the sendCommand function is calling is as follows.

import * as epsonControl from './epson/epsonControl';
import { manufacturerlLookup, commandLookup } from './pjLookups';

function sendCommand(ip, port, model, command, input) {
  const manufacturer = manufacturerlLookup(model);
  const mappedCommand = commandLookup(manufacturer, command, input);
  switch (manufacturer) {
    case 'epson':
      return epsonControl.sendCommand(ip, port, mappedCommand);
    default:
      return null;
  }
}




exports.turnOn = (ip, port, model) => sendCommand(ip, port, model, 'PWR ON');

exports.turnOff = (ip, port, model) => sendCommand(ip, port, model, 'PWR OFF');

exports.shutterOpen = (ip, port, model) => sendCommand(ip, port, model, 'MUTE OFF');

exports.shutterClose = (ip, port, model) => sendCommand(ip, port, model, 'MUTE ON');

exports.setInput = (ip, port, model, input) => sendCommand(ip, port, model, 'SOURCE', input);

exports.getPowerStatus = (ip, port, model) => sendCommand(ip, port, model, 'PWR?');

exports.getShutterStatus = (ip, port, model) => sendCommand(ip, port, model, 'MUTE?');

exports.getInputStatus = (ip, port, model) => sendCommand(ip, port, model, 'SOURCE?');

And finally, the actual function that is async, and is actually returning a promise with return/throw.

exports.sendCommand = async (host, port, command) => {
  log.info(`sending command ${command} to ${host}`);

  // throw new Error('Oh dear!');
  // Set a time to cancel the operation if the connection times out
  const mergedCommand = `${command}\r`;
  // Create a new client
  const socket = new net.Socket();

  socket.setTimeout(1000);

  const client = new PromiseSocket(socket);

  await client.connect({ host, port });

  await client.write(buildBuffer(handshake));

  client.stream.on('data', (data) => {
    if (data.toString() === response) {
      log.verbose(`Got handshake, sending command ${command}`);
      client.write(buildBuffer(mergedCommand));
    } else if (data.toString() === ':') {
      // log.verbose('command executed successfully, disconnecting');
      sendStatus(data.toString(), host);
      client.end();
      return (true, 'command');
    } else {
      const respose = data.toString().substring(0, data.toString().length - 2);
      // log.verbose(`got status ${response} disconnecting`);
      sendStatus(respose, host);
      client.end();
      return (true, 'command', data);
    }
  });
};

Solution

  • I think you're yielding (pausing) the generator function without calling next on it. Saga has functionality for this. For example instead of yielding on a function call, you yield on the saga function call.

    So it should be :

     try {
        let response;
        const command = payload.payload.command;
        if (command === TURNON) {
          response = yield call([pjControl,'turnOn'],ip, port, model);
        } 
     ... 
    

    Please see https://redux-saga.js.org/docs/api/