Very Objective
Very Objective

Reputation: 626

Does setTimeout() run on a separate thread?

Curious to see whether setTimeout() will be fired up asynchronously, I tried the following test script:

function timedText() {
  var x = document.getElementById("txt");
  setTimeout(function() {
    x.value = "1 second"
  }, 1000);
  setTimeout(function() {
    x.value = "2 seconds"
  }, 2000);
  setTimeout(function() {
    x.value = "3 seconds"
  }, 3000);

  while (true) {}
}
<p>Click on the button below. The input field will tell you when two, four, and six seconds have passed.</p>

<button onclick="timedText()">Display timed text</button>
<input type="text" id="txt">

Sure enough, clicking the button causes the browser to hang.

This tells me that setTimeout() does not run on a separate thread.

But on a recent interview, the interviewer suggested otherwise... Does that mean that setTimeout() is browser/implementation dependent?

Upvotes: 10

Views: 8540

Answers (5)

t.niese
t.niese

Reputation: 40882

JavaScript is not multi threaded. Well there are WebWorkers that run in a different thread, but that's not really multi threading, more like multiple processes, that communicate with each other.

As of that the while (true) {} will block the js context, because it is an endless loop.

The setTimeout will register a function for later execution. But at no time code will run in parallel for the same context.

A while (true) itself does not necessarily create a blocking loop:

async function sleep(time) {
  return new Promise((resolve, _) => setTimeout(resolve, time))
}

async function test(val) {
  while (true) {
    console.log('in while loop ' + val)
    await sleep(1000)
  }
}

test('foo')
test('bar')

So you can say with await/async you can create some kind of cooperative multitasking like setup, but still no multi threading

Upvotes: 14

Matt Harrison
Matt Harrison

Reputation: 13597

When you call setTimeout() typically control is passing back into the host environment (the browser or native node.js code for example). What is happening then is that your callback is being registered in a list of timers to execute in the future. setTimeout() will the return back to your code which will continue executing.

When your script finally completes, control again will return to the host environment which has an event loop, this loop keeps spinning until it's finally time to call your registered callback.

You can actually approximate something like this in JavaScript itself by implementing an event loop just for fun:

class EventLoop {

    constructor() {
        this.entries = [];  // a list of all registered callbacks
        this.turns = 0;     // keep track of how many turns of the loop we make
    }

    // Adds a new callback to the list

    schedule(callback, condition) {
        this.entries.push([condition, callback]);
    }

    // To removes a callback when it's been called

    remove(entry) {
        this.entries.splice(this.entries.indexOf(entry), 1);
    }

    // Run the loop until all registered callbacks were called
    // Returns the number of turns it made in the while loop

    run(timeout) {

        this.turns = 0;

        while (this.entries.length) {
            for (const entry of this.entries) {
                const [condition, callback] = entry;
                if (condition()) {
                    callback();
                    this.remove(entry);
                }
            }

            this.turns++;
        }

        return this.turns;
    }

}

We can use this EventLoop to implement something like a setTimeout():

// Define a handy log function

const log = ((startTime) => (text) => {

    console.log(`t+${(Date.now() - startTime).toFixed(3)}ms: ${text}`);

})(Date.now());

// Create an event loop

const loop = new EventLoop();

// Define a setTimeout using the event loop

const defer = (fn, timeout) => {

    const start = Date.now();
    const end = start + timeout;

    loop.schedule(fn, () => Date.now() >= end);
};

// Schedule some nested events

defer(() => {

    log('I run second');

    defer(() => {

        log('I run third');

        defer(() => {

            log('I run fourth');

        }, 200);

    }, 200);

}, 200);

// Log syncronously

log('I run first');

// Start the event loop turning (blocks until all events are complete)

const turns = loop.run();

log(`Loop exited after ${turns} turns`);

// This will log after event loop has finished running

log('I run last');

If you run this with node.js you'll get the following output:

t+0.000ms: I run first t+200.000ms: I run second t+400.000ms: I run third t+600.000ms: I run fourth t+600.000ms: Loop exited after 6441157 turns t+600.000ms: I run last

We just created an asynchronous timeout in pure JavaScript with a single thread. Now in reality you wouldn't do this in JavaScript, the event loop would be implemented in native code and hosted in the host environment. An example of such an event loop is libuv used by Node.js. Libuv can do things more efficiently than our toy JS example, it can put the current thread to sleep (technically it doesn't do this, it polls for IO but same concept) so it's not wasting CPU cycles.

Upvotes: 2

Dmitry Surin
Dmitry Surin

Reputation: 351

This tells me that setTimeout() does not run on a separate thread.

Yes. There is only one thread in JS.

But on a recent interview, the interviewer suggested otherwise... Does that mean that setTimeout() is browser/implementation dependent?

As far as i know only engine changed from browser to browser. Internal mechanism stands the same - event-loop processor.

Upvotes: 2

Shanaka Rusith
Shanaka Rusith

Reputation: 431

Those asynchronous functions are handled by the browser. not the JavaScript engine. there are no threads in JavaScript.

Upvotes: 0

Silvinus
Silvinus

Reputation: 1445

There is no thread in javascript. setTimeout push just the delegate function insto a stack that will pop for the next pass. You can read that JavaScript and Threads

Upvotes: 5

Related Questions