0

Say I have a queue class that's executing a series of functions I've already declared:

class DrawQueue{
    constructor(interval){
        this.sequence = [];
        this.interval=interval?interval:50;
    }
    addFunction=(fn)=>{
        this.sequence.push(fn);
        //throw exception here if not a function
    };
    execFunctions = ()=>{
        let intvl = setInterval(
            ()=>{
                const fn = this.sequence.shift();
                //clear interval & return here if not a function
                fn.call();
            },
            this.interval
        )
    }
}

Now I want to pass it a series of functions that have some values calculated inside them: //I have a single count variable here but the code I'm running is being generated by a user who might have any number of variables that are being updated let count = 0; let counterDiv = document.querySelector('#counter') let dq = new DrawQueue(1000); function startCount(){ //call when window's loaded let countFn=(()=> { let innerFn= function(){ let str = (function(){ return count.toString() })(); counterDiv.innerHTML=str; } //imagine that any number of count variables might be being updated somewhere in the function count++; dq.addFunction(innerFn); })

  while(count<10){
    countFn();
  } 
  dq.execFunctions();
}

Right now this immediately sets the counter div to 10, and then keeps setting it to 10 ten more times. But I want to assign the str variable's value before I pass the functions. So the first function I pass sets the counter to 1, then 2 and so on.

I was trying to set the let str= function(... using an iife, but that didn't work.

One solution that I know would work is to make the whole function a string and then run it with eval but I really do not want to use eval unless I absolutely have to.

Is there any other way to pass these functions with certain variables already "locked in", meaning they're assigned before the function is placed in the queue?

UPDATE: To clarify, this is just a simplified version of a more complex example. In the actual example, the code is dynamically generated by another user, so in addition to 'count' any number of other values might need to be evaluated. So passing the count variable, as several good answers have suggested, is not going to work.

FURTHER CLARIFICATION: What I'm saying above is that because the user could be generating any number of variables that will be updated as the code runs, I can't pass those variables as arguments. (imagine there might be a count2, count3...countn and I don't know how many or where they'll be used or updated in the code.

FURTHER UPDATE: so a commenter wants to see the code in which this applies so here goes. It is an application using Blockly and P5 play, where users will be making code with blocks to move a sprite. So the code for the blocks might be something like this (yes this code is really ugly because it's just a test, but you asked to see it):

function moveForward(sprite){
    let dir = ship.rotation* (Math.PI / 180);
    let deltaX = Math.cos(dir)*5;
    let deltaY = Math.sin(dir)*5;
    let newX = ship.position.x + deltaX;
    let newY = ship.position.y + deltaY;
    ship.position.x=newX;
    ship.position.y=newY;
    redraw();
}

function moveBackward(sprite){
    let dir = ship.rotation* (Math.PI / 180);
    let deltaX = Math.cos(dir)*5;
    let deltaY = Math.sin(dir)*5;
    let newX = ship.position.x - deltaX;
    let newY = ship.position.y - deltaY;
    ship.position.x=newX;
    ship.position.y=newY;
    redraw();
}

function turnLeft(sprite){
    let newDir=ship.rotation-90;
    ship.rotation=newDir;
    redraw();
}

function turnRight(sprite){
    let newDir=ship.rotation+90;
    ship.rotation=newDir;
    redraw();
}

There will be any number of other sprites, each with 20 or so properties that could be updated.

Now if I just put all these functions in a row, the sprite will just immediately jump to where the code would put it. Because, you know, normally we want computers to do things as fast as they can.

But since this is made for teaching, I want the user to see the canvas updating step by step, with a delay between each redraw. That means every sprite will have its x and y coordinates, along with color and rotation and a bunch of other things, change slowly.

So the purpose of the DrawQueue to execute the drawing update steps slowly with a setInterval and update the canvas at any interval I want. I can't just run every single command with a setInterval because there could be logic or loops in there. The only thing I want to go in the interval is the updates to the canvas, anything else can happen as fast as it wants.

So imagine the four functions I provided above, along with any number of other functions and modifications to the properties of any number of other sprites or properties of the canvas.

jimboweb
  • 4,362
  • 3
  • 22
  • 45
  • count.toString() is not going to be the value when you call the function. It is going to be the value of whatever count is when it runs. – epascarello Apr 24 '19 at 20:33
  • Yes, thank you. I realize that is what's happening; I'm trying to figure out how to evaluate the value before I add the function to the queue. – jimboweb Apr 24 '19 at 20:34
  • You have to store it into another variable. – epascarello Apr 24 '19 at 20:34
  • Not quite sure what you mean. If I put it in a different variable that's still set when I run the function, just like the `str` variable is. Could you provide an example to clarify? – jimboweb Apr 24 '19 at 20:37
  • It's not the same but maybe [this answer](https://stackoverflow.com/a/7749123/863110) can give you a direction to the solution. – Mosh Feu Apr 24 '19 at 20:37
  • I don't understand the "UPDATE" section. Could you please provide code in your question that we can check and use in our answers? – trincot Apr 24 '19 at 20:48
  • 1
    To be honest, you leave us guessing. Can you not provide a concrete case where such extra variables exist, so we can see what their scope is intended to be, and which functions should have access to the *same* or *different* instance of those variables? It is quite ambiguous right now... – trincot Apr 24 '19 at 21:03
  • I honestly think you are approaching this totally wrong. – epascarello Apr 25 '19 at 04:31
  • You might well be right. If the approach I'm talking about here can't be done I'll try it a different way. I am open to that possibility. – jimboweb Apr 25 '19 at 17:15

2 Answers2

0

By the time the innerFn is actually called, the count variable has already increased to its final value.

To give each innerFn instance its own value for count, you could bind it as function argument:

let innerFn = function(count) {  //<--- argument
  let str = (function(){
    return count.toString()
  })();
  counterDiv.innerHTML=str;
}.bind(null, count); // pass the global count into a bound argument

NB: make sure to check in your class that fn is defined (as the array will become empty at some point).

class DrawQueue{
    constructor(interval){
        this.sequence = [];
        this.interval=interval?interval:50;
    }
    addFunction(fn){
        this.sequence.push(fn);
        //throw exception here if not a function
    };
    execFunctions(){
        let intvl = setInterval(
            ()=>{
                const fn = this.sequence.shift();
                //clear interval & return here if not a function
                if (fn) fn.call();
            },
            this.interval
        )
    }
}

let count = 0;
let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);

function startCount(){ //call when window's loaded
  let countFn=(()=>
  {
    let innerFn= function(count){
      let str = (function(){
        return count.toString()
      })();
      counterDiv.innerHTML=str;
    }.bind(null, count);

    count++;
    dq.addFunction(innerFn);
  })

  while(count<10){
    countFn();
  } 
  dq.execFunctions();
}

window.onload = startCount;
<div id="counter"></div>

Even better would be to avoid a reference to a global variable, and pass count to the countFn function as parameter:

let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);
function startCount(){ //call when window's loaded
  let countFn=((count)=> // local variable
  {
    let innerFn= function(){
      let str = (function(){
        return count.toString()
      })();
      counterDiv.innerHTML=str;
    }

    dq.addFunction(innerFn);
  })

  for(let count = 0; count<10; count++){ // local variable
    countFn(count); // pass it
  } 
  dq.execFunctions();
}

Addendum

In your question's update you speak of more variables. In that case, pass an object around, which can have many properties, possibly even managed completely by the user-provided code:

let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);
function startCount(){ //call when window's loaded
  let countFn=((state)=> // local variable with count property
  {
    let innerFn= function(){
      let str = (function(){
        return state.count.toString()
      })();
      counterDiv.innerHTML=str;
    }

    dq.addFunction(innerFn);
  })

  for(let count = 0; count<10; count++){ // local variable
    const state = {};    
    state.count = count;
    countFn(state); // pass it
  } 
  dq.execFunctions();
}

Depending on your expectations you should either use the same state object or create new state variables (within the loop, or even deeper in the execution context). This all depends on how you want the system to behave.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Thank you so much. These do probably solve the problem as I described it in the example, but in the situation I'm simulating the number of values that need to be calculated change because I'm executing code that another user generated. I will add clarification to the question. – jimboweb Apr 24 '19 at 20:44
  • I don't understand what you mean. If there are more variables involved, just apply the same principle to them. Or are you saying the user-provided code can access *any* global variable without you knowing it? – trincot Apr 24 '19 at 20:51
  • I can't provide the code because I haven't written it yet, this is a proof of concept I'm doing before I build it. What I'm saying is I don't know what variables the user might be generating or updating. In this case just imagine the user might also generate a `count2`, `count3`...`countn` and be updating all of them. – jimboweb Apr 24 '19 at 20:55
  • See addition to my answer. The idea is to have all those variables as properties of a `state` object, which you can pass around. – trincot Apr 24 '19 at 20:58
0

The problem you have is the value is not stored at the time you make the function. It is just a reference to a variable that you are updating. So when it calls, it is reading that variable.

You would need to pass it into the method so you can store the state of the variable at that moment in time.

let count = 0;
let counterDiv = document.querySelector('#counter')
let dq = new DrawQueue(1000);
function startCount(){ //call when window's loaded
  let countFn=((count)=> . // <-- reference it here
  {
    let innerFn= function(){
      let str = (function(){
        return count.toString()
      })();
      counterDiv.innerHTML=str;
    }
    dq.addFunction(innerFn);
  })

  while(count<10){
    countFn(count++);  // <-- update it here
  } 
  dq.execFunctions();
}
epascarello
  • 204,599
  • 20
  • 195
  • 236