Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

setTimeout returns error with Uncaught TypeError: Illegal invocation in AudioContext in Chrome

In Chrome I first create a continuous tone with the AudioContext:

var audioCtx = new (window.AudioContext || window.webkitAudioContext);

var oscillator = audioCtx.createOscillator();
var gainNode = audioCtx.createGain();

oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);

oscillator.start();

Now I want to stop it after a few milliseconds. So I do this:

setTimeout(oscillator.stop, 500)

This returns the error Uncaught TypeError: Illegal invocation.

However if I do;

setTimeout(function(){oscillator.stop()}, 500)

it works fine.

I would like to now why the first one doesn't work and returns an error. It seems like the straightforward way to do this.

like image 285
Houshalter Avatar asked Apr 15 '15 02:04

Houshalter


1 Answers

Your original code doesn't work because the stop function is passed to setTimeout without any context - it does not know which object it is supposed to act on. If you invoke it like this:

oscillator.stop();

Then within stop, the special variable this is set to the object pointed to by oscillator. But if you refer to it like this:

var x = oscillator.stop;

The function is not actually invoked. Rather, a reference to the function is simply extracted from oscillator and stored elsewhere. The function does not remember where it came from, and could be stored in many different variables or object properties at the same time. For example:

var x = {};
x.foo = oscillator.stop;
x.foo();

The last line calls stop with a context of x (this is set to x) rather than oscillator. (The function's body will cause errors since stop makes assumptions about what its context looks like, but the call itself is legal.) Alternatively, if you do this:

var foo = oscillator.stop;
foo();

Then stop will be called with only a default context. In strict mode, this will be set to undefined, and in non-strict mode, this will be set to window.

When you do this:

setTimeout(function(){oscillator.stop()}, 500)

The anonymous function invokes stop with the proper context. If, as suggested by @elclanrs in comments, you do this:

setTimeout(oscillator.stop.bind(oscillator), 500)

It is effectively the same thing: an anonymous function is created that calls stop with a context of oscillator.

like image 76
radiaph Avatar answered Sep 22 '22 19:09

radiaph