21

I trying to wrap my head around setTimeout, but I can't get it to work properly.

I have set up an example here: http://jsfiddle.net/timkl/Fca2n/

I want a text to countdown after an anchor is clicked - but my setTimeout seems to fire at the same time, even though I've set the delay to 1 sec.

This is my HTML:

<a href="#">Click me!</a>

<span id="target"></span>

This is my JS:

$(document).ready(function() {


function foo(){

    writeNumber = $("#target");
    
    setTimeout(writeNumber.html("1"),1000);
    setTimeout(writeNumber.html("2"),1000);
    setTimeout(writeNumber.html("3"),1000);
    };

$('a').click(function() {
 foo();
});

});
timkl
  • 3,299
  • 12
  • 57
  • 71

7 Answers7

53

setTimeout takes a function as an argument. You're executing the function and passing the result into setTimeout (so the function is executed straight away). You can use anonymous functions, for example:

setTimeout(function() {
    writeNumber.html("1");
}, 1000);

Note that the same is true of setInterval.

James Allardice
  • 164,175
  • 21
  • 332
  • 312
  • Thx for helping me out! :) I've updated my jsfiddle: http://jsfiddle.net/timkl/cRDQh/ I still get the same result, the setTimeouts fires at the same time. – timkl Nov 19 '11 at 18:35
  • 1
    No problem :) If you don't want them to fire at the same time, change the timeout lengths. For example, 1000ms for the first timeout, 2000ms for the second and so on. – James Allardice Nov 19 '11 at 18:36
  • The same is true not only for `setTimeout` and `setInterval`, but for every case you are supposed to pass callback as one of the parameters. – Tadeck Nov 19 '11 at 18:37
  • @Tadeck - Yes, good point. With the exception of when executing the function returns a function :) – James Allardice Nov 19 '11 at 18:39
  • @JamesAllardice: Agreed, but in this case you are still passing a callback (even though it is a result of execution of another function). It gets more complex, but the clue is to pass **a function** we want to be executed after specified time period (or with a given interval) and not execute it at the time it is passed. – Tadeck Nov 19 '11 at 18:43
7

You need to wrap your statements in anonymous functions and also stagger your timings -

setTimeout(function(){writeNumber.html("1")},1000);
setTimeout(function(){writeNumber.html("2")},2000);
setTimeout(function(){writeNumber.html("3")},3000);

If you set everything to 1000 the steps will pretty much run simultaneously as the setTimeout function will run the task 1 second after you called the function not 1 second after the previous call to the setTimeout function finished.

Demo - http://jsfiddle.net/JSe3H/1/

ipr101
  • 24,096
  • 8
  • 59
  • 61
  • +1 Good point - you pointed out not only the way callback is passed, but also when it is executed. – Tadeck Nov 19 '11 at 18:41
3

You need to use a function reference to be invoked later when the timer expires. Wrap each statement in an anonymous function so that it isn't executed immediately, but rather when the timer expires.

setTimeout(function() { writeNumber.html("1"); },1000);

Also, you want to use a different delay value for each one so that the timers don't expire at the same time. See an updated fiddle at http://jsfiddle.net/RqCqM/

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
2

There is a provision to pass arguments to the function. In your case, you can do it by

setTimeout(writeNumber.html,1000,1);
setTimeout(writeNumber.html,1000,2);
setTimeout(writeNumber.html,1000,3);

third argument to setTimeout function will be pass to writeNumber.html function

Achal Saraiya
  • 410
  • 5
  • 15
2

You need tot use a functions to be called after the timeout is passed; you can use anonymous function too, then your function foo will look like this:

function foo(){

writeNumber = $("#target");

setTimeout(function() { writeNumber.html("1"); },1000);
setTimeout(function() { writeNumber.html("2"); },1000);
setTimeout(function() { writeNumber.html("3"); },1000);

};
Irvin Dominin
  • 30,819
  • 9
  • 77
  • 111
1

Just use setInterval(). Here's what I came up with. Here's your new javascript:

function foo(){
    writeNumber = $("#target");
    number      = 0;
    writeNumber.html(number);
    setInterval(function(){
        number = number+1;
        writeNumber.html(number);
    },1000);
    };
$('a').click(function() {
 foo();
});
Purag
  • 16,941
  • 4
  • 54
  • 75
0

I landed on this question. It has been answered adequately, and I think using setInterval as @Purag suggested is probably the best approach to get the desired functional behaviour. However the initial code example did not take JavaScript's asynchroneous behaviour into account. This is an often occurring error, which I've made myself on more than occasion :).

So as a side note I wanted to give another possible solution for this which mimics the initial attempt, but this time DOES consider Javascript's Asynchronousity:

setTimeout(function() {
    writeNumber.html("1");
    setTimeout(function() {
        writeNumber.html("1");
        setTimeout(function() {
            writeNumber.html("1");
        }, 1000);
    }, 1000);
}, 1000);

Now ofcourse this is clearly terrible code!

I have given a working JSFiddle of it in my own SO question. This code exemplifies the so-called pyramid of doom. And this can be mitigated by using JavaScript promises, as shown in the answers to my question. It takes some work to write a version of WriteNumber() that uses Promises, but then the code can be rewritten to somehting like:

writeNumAsync(0)
    .then(writeNumAsync)
    .then(writeNumAsync)
    .then(writeNumAsync);
Community
  • 1
  • 1
Bart
  • 5,065
  • 1
  • 35
  • 43