By Alex Hwang


2010-09-27 01:03:48 8 Comments

Simply put...

why does

setTimeout('playNote('+currentaudio.id+', '+noteTime+')', delay);

work perfectly, calling the function after the the specified delay, but

setTimeout(playNote(currentaudio.id,noteTime), delay);

calls the function playNote all at the same time?

(these setTimeout()s are in a for loop)

or, if my explanation is too hard to read, what is the difference between the two functions?

5 comments

@Runny-Yolk 2017-07-27 01:35:26

I literally created an account on this site to comment on Peter Ajtai's answer (currently highest voted), only to discover that you require 50 rep (whatever that is) to comment, so I'll do it as an answer since it's probably worth pointing out a couple things.

In his answer, he states the following:

You can also pass setTimeout a reference, since a reference isn't executed immediately, but then you can't pass arguments:

setTimeout(playNote, delay);

This isn't true. After giving setTimeout a function reference and delay amount, any additional arguments are parsed as arguments for the referenced function. The below would be better than wrapping a function call in a function.

setTimeout(playNote, delay, currentaudio.id, noteTime)

Always consult the docs.

That said, as Peter points out, a recursive function would be a good idea if you want to vary the delay between each playNote(), or consider using setInterval() if you want there to be the same delay between each playNote().

Also worth noting that if you want to parse the i of your for loop into a setTimeout(), you need to wrap it in a function, as detailed here.

@Peter Ajtai 2010-09-27 01:12:02

The first form that you list works, since it will evaluate a string at the end of delay. Using eval() is generally not a good idea, so you should avoid this.

The second method doesn't work, since you immediately execute a function object with the function call operator (). What ends up happening is that playNote is executed immediately if you use the form playNote(...), so nothing will happen at the end of the delay.

Instead, you have to pass an anonymous function to setTimeout, so the correct form is:

setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);

Note that you are passing setTimeout an entire function expression, so it will hold on to the anonymous function and only execute it at the end of the delay.

You can also pass setTimeout a reference, since a reference isn't executed immediately, but then you can't pass arguments:

setTimeout(playNote, delay);

Note:

For repeated events you can use setInterval() and you can set setInterval() to a variable and use the variable to stop the interval with clearInterval().

You say you use setTimeout() in a for loop. In many situations, it is better to use setTimeout() in a recursive function. This is because in a for loop, the variables used in the setTimeout() will not be the variables as they were when setTimeout() began, but the variables as they are after the delay when the function is fired.

Just use a recursive function to sidestep this entire problem.

Using recursion to deal with variable delay times:

  // Set original delay
var delay = 500;

  // Call the function for the first time, to begin the recursion.
playNote(xxx, yyy);

  // The recursive function
function playNote(theId, theTime)
{
    // Do whatever has to be done
    // ...

    // Have the function call itself again after a delay, if necessary
    //   you can modify the arguments that you use here. As an
    //   example I add 20 to theTime each time. You can also modify
    //   the delay. I add 1/2 a second to the delay each time as an example.
    //   You can use a condition to continue or stop the recursion

    delay += 500;

    if (condition)
    { setTimeout(function() { playNote(theID, theTime + 20) }, delay); }
}

@Kranu 2010-09-27 02:18:27

Detailed and accurate.. +1

@Doin 2016-05-12 18:17:13

Strictly speaking, that's not recursion, since the function isn't calling itself directly, it's just queuing up another call to itself to execute later. Critically, each call will return before the next is initiated.

@Doin 2016-05-12 18:20:27

A big problem with your "recursive" code is that due to the way closures work, each successive call to playNote will add an entry onto a closure chain whose length will increase indefinitely. Like infinite recursion, that's a BAD IDEA - you'll eventually run out of memory! I've edited the answer to show how to avoid this, while preserving the method in general.

@bobince 2010-09-27 02:15:12

Don't use string-timeouts. It's effective an eval, which is a Bad Thing. It works because it's converting currentaudio.id and noteTime to the string representations of themselves and hiding it in the code. This only works as long as those values have toString()s that generate JavaScript literal syntax that will recreate the value, which is true for Number but not for much else.

setTimeout(playNote(currentaudio.id, noteTime), delay);

that's a function call. playNote is called immediately and the returned result of the function (probably undefined) is passed to setTimeout(), not what you want.

As other answers mention, you can use an inline function expression with a closure to reference currentaudio and noteTime:

setTimeout(function() {
    playNote(currentaudio.id, noteTime);
}, delay);

However, if you're in a loop and currentaudio or noteTime is different each time around the loop, you've got the Closure Loop Problem: the same variable will be referenced in every timeout, so when they're called you'll get the same value each time, the value that was left in the variable when the loop finished earlier.

You can work around this with another closure, taking a copy of the variable's value for each iteration of the loop:

setTimeout(function() {
    return function(currentaudio, noteTime) {
        playNote(currentaudio.id, noteTime);
    };
}(currentaudio, noteTime), delay);

but this is getting a bit ugly now. Better is Function#bind, which will partially-apply a function for you:

setTimeout(playNote.bind(window, currentaudio.id, noteTime), delay);

(window is for setting the value of this inside the function, which is a feature of bind() you don't need here.)

However this is an ECMAScript Fifth Edition feature which not all browsers support yet. So if you want to use it you have to first hack in support, eg.:

// Make ECMA262-5 Function#bind work on older browsers
//
if (!('bind' in Function.prototype)) {
    Function.prototype.bind= function(owner) {
        var that= this;
        if (arguments.length<=1) {
            return function() {
                return that.apply(owner, arguments);
            };
        } else {
            var args= Array.prototype.slice.call(arguments, 1);
            return function() {
                return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
            };
        }
    };
}

@dgnorton 2010-09-27 01:06:53

Because the second one you're telling it to call the playNote function first and then pass the return value from it to setTimeout.

@Daniel A. White 2010-09-27 01:06:03

Try this.

setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);

Related Questions

Sponsored Content

31 Answered Questions

[SOLVED] How do I return the response from an asynchronous call?

38 Answered Questions

[SOLVED] Detecting an "invalid date" Date instance in JavaScript

  • 2009-08-30 11:34:40
  • orip
  • 598737 View
  • 1143 Score
  • 38 Answer
  • Tags:   javascript date

38 Answered Questions

[SOLVED] Is there an "exists" function for jQuery?

  • 2008-08-27 19:49:41
  • Jake McGraw
  • 682418 View
  • 2423 Score
  • 38 Answer
  • Tags:   javascript jquery

34 Answered Questions

[SOLVED] var functionName = function() {} vs function functionName() {}

17 Answered Questions

[SOLVED] Why is setTimeout(fn, 0) sometimes useful?

20 Answered Questions

[SOLVED] What is the difference between call and apply?

12 Answered Questions

[SOLVED] JavaScript closures vs. anonymous functions

13 Answered Questions

[SOLVED] Is the recommendation to include CSS before JavaScript invalid?

19 Answered Questions

25 Answered Questions

[SOLVED] Is Safari on iOS 6 caching $.ajax results?

Sponsored Content