Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use setTimout in Coffeescript within a loop

window.onload = ->

  boxOrig1 = 10
  boxOrig2 = 30
  canvasW = 400
  canvasH = 300

  ctx = $("#canvas")[0].getContext('2d');

  draw = (origin,dimension) ->    
    ctx.clearRect(0, 0, canvasW, canvasH)
    ctx.fillStyle = 'rgb(200,0,0)'  
    ctx.fillRect(origin + boxOrig1, boxOrig1, dimension, dimension)  
    ctx.fillStyle = 'rgba(0, 0, 200, 0.5)'  
    ctx.fillRect(origin + boxOrig2, boxOrig2, dimension, dimension)

  for m in [10..100] by 10
    t = setTimeout (-> draw(m, 150)), 1000 
    t.clearTimeout
#     draw(m,150)
#     alert m

As an exercise, the code above is meant to draw a little design on a canvas, pause for a second, then redraw it again 10 pixels to the right.

I can see that the mechanics work fine when I interrupt the loop with an alert (as in those last two commented lines), but I'm not getting the expected behavior with the setTimeout function. The design just appears at the rightmost position after the timeout, skipping the incremental steps in between.

I've tried many different ways of doing this from other examples, but it's just melting my brain. Any suggestions?

like image 837
user105090 Avatar asked Nov 29 '11 22:11

user105090


People also ask

How do you run a setTimeout in a for loop?

Using the ES6 let keyword inside the for loop, creates a separate scope for each iteration making it possible to print the consecutive variable value. IIFE can be used to create a new scope for each setTimeout callback without polluting the global scope. Simply wrap up the setTimeout code inside an IIFE.

Can I use setTimeout in a loop JavaScript?

setTimeout function in JavaScript usually takes a callback function as an argument. A callback function is a function that is executed after another function finishes running. In this case, it will run after for loop finishes.

How do you stop a setTimeout loop?

To cancel a setTimeout() method from running, you need to use the clearTimeout() method, passing the ID value returned when you call the setTimeout() method.


2 Answers

Geoff has outlined one approach (using setInterval and clearing it from the callback), so I'll outline the other: Using setTimeout from the callback. Something like

m = 10
do drawCallback = ->
  draw m, 150
  m += 10
  setTimeout drawCallback, 1000 unless m > 100

Note that there is a subtle timing difference between the two approaches that you should be aware of: setInterval func, 1000 will run the function once every 1000ms; the chained setTimeout will put a 1000ms delay between each function call. So if draw took 100ms, say, the chained setTimeout would be equivalent to setInterval func, 1100. It probably doesn't matter, but it's worth being aware of.

Bonus approach: You don't have to abandon your loop; you could just set all the timeouts from it at once:

for m in [10..100] by 10
  do (m) ->
    setTimeout (-> draw(m, 150)), 100 * m

The do (m) is necessary so that the closure passed to setTimeout sees each value of m, not just its final value in the loop. See my article A CoffeeScript Intervention for more info on this.

Finally: I know this all seems very confusing at first, but timing in JS is actually very simple because the language is single-threaded. That means that events you schedule with setTimeout or setInterval or any other async function will never occur during a loop, even if the loop is infinite. They only occur after all of your code has finished executing. I talk about this in a little more detail in my book on CoffeeScript.

like image 106
Trevor Burnham Avatar answered Nov 02 '22 14:11

Trevor Burnham


This might be expressed more intuitively as a setInterval:

window.onload = ->

  boxOrig1 = 10
  boxOrig2 = 30
  canvasW = 400 
  canvasH = 300 

  ctx = document.getElementById("canvas").getContext('2d')

  draw = (origin,dimension) ->
    ctx.clearRect(0, 0, canvasW, canvasH)
    ctx.fillStyle = 'rgb(200,0,0)'
    ctx.fillRect(origin + boxOrig1, boxOrig1, dimension, dimension)
    ctx.fillStyle = 'rgba(0, 0, 200, 0.5)'
    ctx.fillRect(origin + boxOrig2, boxOrig2, dimension, dimension)

  count = 10
  timer = setInterval (-> 
    if count == 100
        clearInterval(timer)
    draw(count, 150); count+=10
  ), 1000
like image 32
Geoff Moller Avatar answered Nov 02 '22 14:11

Geoff Moller