2

I am trying to write a GUI frontend that uses a service to get data about the system. I am using a net.Socket for the client end of this. I want to be able to access certain variables assigned in the data event handlers in other modules but the assignment does not stay after that callback function finishes.

Problematic code:

client.on('data', (data) => {
      var array = [...data];
      array.splice(0,2);
      for (var i=0;i<array.length;i++) {
        dataInBuffer = dataInBuffer + String.fromCharCode(array[i]);
      }
      console.log(dataInBuffer);
      if (dataInBuffer.startsWith('batStat')) {
        let lastBatteryJSON = JSON.parse(dataInBuffer.split(';')[1]);
        module.exports.hasBattery = lastBatteryJSON.hasBattery == 'true';
        module.exports.isCharging = lastBatteryJSON.isCharging == 'true';
        module.exports.lastBatteryReading = parseFloat(lastBatteryJSON.batteryLife);
      }
      dataInBuffer = '';
    });

Those three exported variable assignments don't actually work, the variables always either stay undefined or their default values outside of the function. I tried using a Promise to solve this problem but got the same result. I'm at a loss and I can't find any other questions or forum posts that solve this problem.

EDIT I do not have the option of moving the code that depends on those variables into the callback. In order to do that I would have to wait for the data every frame and flood the server as a result.

7
  • You can't just change exports on the fly. When you import a module, its contents are executed and exports exported. You can't export asynchronously. Commented Apr 18, 2018 at 1:30
  • @Li357 ok but is there a way to get the information out of the function? Commented Apr 18, 2018 at 1:35
  • Has the console logged the data received before you check the exports' 'hasBattery', 'isCharging' and lastBatteryReading properties outside the module? What happens if you set these properties, of the exports object, to "unknown" before setting the on data callback for client? Commented Apr 18, 2018 at 1:43
  • @traktor53 the properties just stay whatever they were initially defined as then. The callback doesn't seem to change them. Commented Apr 18, 2018 at 1:46
  • 1
    @GalenNare IIRC, if you export a object, say let data = {}; module.exports.data = data. than you can set data.x and other module would able to receive it. But why you do not want callback/async function/Promise? it seems fit what you need. Commented Apr 18, 2018 at 1:48

2 Answers 2

1

As apple commented; you can export an object and mutate it every time you receive data:

const data = {};
client.on('data', (data) => {
  var array = [...data];
  array.splice(0, 2);
  for (var i = 0; i < array.length; i++) {
    dataInBuffer = dataInBuffer + String.fromCharCode(array[i]);
  }
  console.log(dataInBuffer);
  if (dataInBuffer.startsWith('batStat')) {
    let lastBatteryJSON = JSON.parse(dataInBuffer.split(';')[1]);
    //mutate the data object
    data.hasBattery = lastBatteryJSON.hasBattery == 'true';
    data.isCharging = lastBatteryJSON.isCharging == 'true';
    data.lastBatteryReading = parseFloat(lastBatteryJSON.batteryLife);
  }
  dataInBuffer = '';
});
//export the data object
module.exports.batteryData = data;

Or as CertainPerformance answered you can have the caller decide when to ask for the information and provide a promise.

Here is an extended version of CertainPerformance answer that listens to error as well so a promise can be rejected and cleans up the event listeners when promise is resolved or rejected:

//wrapper for client.on to add and remove event listeners
const listeners = (function(){
  var listenerCounter = -1;
  const listeners = [];
  const triggerEvent = event => data =>{
    listeners.filter(
      listener=>listener[2] === event
    ).forEach(
      listener=>listener[1](data)
    );
  };
  client.on('data', triggerEvent("data"));
  client.on('error', triggerEvent("error"));//assuming you have an error event
  return {
    add:(event,fn)=>{
      listenerCounter = listenerCounter + 1;
      if(listenerCounter>1000000){
        listenerCounter=0;
      }
      listeners.push([listenerCounter,fn,event]);
      return listenerCounter;
    },
    remove:num=>{
      listeners = listeners.filter(
        listener=>{
          num !== listener[0];
        }
      )
    }
  }
}());

//convert data to object or false
const getObjectFromData = data => {
  var array = [...data];
  var dataInBuffer="";
  array.splice(0,2);
  for (var i=0;i<array.length;i++) {
    dataInBuffer = dataInBuffer + String.fromCharCode(array[i]);
  }
  console.log(dataInBuffer);
  if (dataInBuffer.startsWith('batStat')) {
    let lastBatteryJSON = JSON.parse(dataInBuffer.split(';')[1]);
    return {
      hasBattery : lastBatteryJSON.hasBattery == 'true',
      isCharging : lastBatteryJSON.isCharging == 'true',
      lastBatteryReading : parseFloat(lastBatteryJSON.batteryLife)
    };
  }
  return false;
}

//export this function
const getBatteryData = () =>  
  new Promise((resolve,reject) => {
    const removeListeners = ()=>{
      listeners.remove(okId);
      listeners.remove(errorId);
    }
    const okId = listeners.add(
      "data",
      data=>{
        const resultObject = getObjectFromData(data);
        if(resultObject){
          resolve(data);
          removeListeners();//clean up listeners
        }else{
          //not sure of on data is triggered multiple times by client.on.data
          //  if it is then at what point do we need to reject the returned promise?
        }
      }
    )
    const errorId = listeners.add(
      "error",
      error=>{
        reject(error);
        removeListeners();//clean up listeners
      }
    )
  });

  //you can call getBatteryData like so:
  //getBatteryData()
  // .then(batteryData=>console.log(batteryData))
  // .catch(error=>console.warn("an error getting battery data:",error))
Sign up to request clarification or add additional context in comments.

3 Comments

This still doesn't seem to work. The object still doesn't mutate inside of the callback. Any references to it are undefined.
@GalenNare Consider the following: var lucky = tomorrowsWinningLotteryNumbers; If you immediately know what the value of lucky is then please tell me, I'd like to know. You are exporting data but the properties of data are set some time in the future, when they are set you don't know because you mutate them sometime in the future. If you use CertainPerformance answer you have a function that when called returns a promise that resolves to the values you want when they are available The only problem with that answer is that you keep adding event listeners and never remove them.
@GalenNare I have updated the answer with code that will return a promise but won't keep adding event listeners.
1

Your module should export a function that returns a promise that returns the desired values. Also, use const and not var when possible:

let resolveObj;
const haveData = new Promise((resolve) => {
  let resolved = false;
  client.on('data', (data) => {
    const array = [...data];
    array.splice(0, 2);
    for (let i = 0; i < array.length; i++) {
      dataInBuffer = dataInBuffer + String.fromCharCode(array[i]);
    }
    console.log(dataInBuffer);
    if (dataInBuffer.startsWith('batStat')) {
      const {
        hasBattery,
        isCharging,
        batteryLife,
      } = JSON.parse(dataInBuffer.split(';')[1]);
      resolveObj = {
        hasBattery: hasBattery === 'true',
        isCharging: isCharging === 'true',
        lastBatteryReading: Number(batteryLife),
      };
      if (!resolved) resolve();
      resolved = true;
    }
    dataInBuffer = '';
  });
});
const getData = () => haveData.then(() => resolveObj);
module.exports = getData;

Then consume with

moduleFunction().then(({ hasBattery, isCharging, lastBatteryReading }) => {
  // do something with results
});

If called before resolveObj is populated, the promise will wait until the first client.on('data' to resolve. After that, the function will return a promise that resolves immediately to the current value of resolveObj (which will be properly updated on client.on('data')

3 Comments

Maybe better to change haveData to a function so the caller can get latest information: const haveData = () => new Promise... Also remove the on data event listener when resolving the promise.
What is moduleFunction() in this scenario?
@GalenNare It's the imported function from the module all the code in your snippet and my upper snippet is in. import moduleFunction from './getBatteryData.js or something like that, name it whatever is most appropriate

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.