If you’re coming from a programming language that has support for multithreading and concurrency, (such as Java) then understanding the flow of asynchronous events in JavaScript such as timers can be a bit confusing.
However, once you understand the single-threaded nature of most JavaScript, things may actually becoming easier as you don’t have to worry about parallel access to non-local variables.
Let’s take a look at how JavaScript executes when a function is passed to window.setTimeout()
.
Multithreading
As anyone who’s done a fair amount of Java will know, it’s fairly easy to easy to spawn a new Thread, usually by submitting a new Runnable
to a Thread Pool or ExecutorService
, or less frequently, by directly creating an instance of Thread
and calling start()
. Since this post isn’t about Java, I won’t talk anymore about it, other than to say when you do this, you have created/spawned another thread – so you now have at least two threads: The one you just created and the original/main one that created the second thread.
The first thing you’ll realize is that with multiple threads executing, by default there is no defined order to how they will execute. So, if I spawned two threads, one printing "a"
to stdout
repeatedly and one printing "b"
to stdout
, there is no guarantee of the order the letters would appear on screen and furthermore, it would likely vary from execution to execution of the program.
This leads to the need for synchronization and locks on concurrent code in order to maintain a certain level of ordering in how concurrent code operates. Is the same true for JavaScript?
JavaScript Timers
I will focus my example on the setTimeout()
timer, though the same principle I’l be demonstrating holds for other asynchronous events.
What will be the value of output
after the following code has executed?
var output = '';
var iterations = 10000000;
setTimeout(function() {
for (var i = 0; i < iterations; ++i) {
output += 'a';
}
}, 100);
setTimeout(function() {
for (var i = 0; i < iterations; ++i) {
output += 'b';
}
}, 100);
In this example, it would seem that we have kicked off the execution of two long-running functions at approximately the same time and both are modifying/mutating a global/shared variable.
If this example involved multithreaded execution, the value of output
would have several alternations between the letters ‘a’ and ‘b’.
Conversely, if output
consisted of a solid block of ‘a’ followed by a solid block of ‘b’, we could conclude that it was likely the first function executed completely before the second function was allowed to execute.
In my brief tests in Chrome 33.0 and Firefox 27.0 this was the case: A solid block of ‘a’, followed by a solid block of ‘b’. (You can see the full example/test in this gist.)
This would seem to indicate that there is only a single thread of execution in JavaScript, as the execution of one function completely blocked the execution of another.
A little deeper
What’s really going on is that the calls to setTimeout
are asynchronous. This means they return (almost) immediately and instead just queue up an event to invoke the passed-in function later. This means that the function that writes ‘a’ to output
will execute before the function that outputs ‘b’.
How the queue works is likely implementation-specific, but when an event is read off the queue and processed, it blocks while that event/function is being executed.
This behaviour doesn’t just apply for JavaScript timers like setTimeout/setInterval
but for all asynchronous event handling in JavaScript.
This is probably a key point: Asynchronous does not mean parallel! In the case of JavaScript, the concurrency is actually accomplished through this sort of context-switching, rather than having separate threads that actually execute/run in in parallel/at the same time.
Web Workers
One important caveat: The above discussion did not involve Web Workers, which is an API that does allow you to explicitly create separate threads of processing in JavaScript. However, you must explicitly spawn these new threads and they can only communicate with the main thread through the use of specific APIs/event handlers.
By default, variables in the main thread aren’t shared with the worker threads. I suspect this was done to avoid problematic concurrent data-access issues.
However, the Web Workers API does not affect how events such as setTimeout
work – everything above still holds.
References
- How JavaScript Timers Work
- Minimum/maximum delay and timeout nesting
- Introduction to HTML5 Web Workers: The JavaScript Multi-threading Approach
- JavaScript’s Strange Threaded Nature