10

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?

Very Objective
  • 601
  • 7
  • 16
  • Whats the while(true)-loop or the int=0 for? – Ahorn Aug 22 '18 at 06:29
  • @WhiteMaple `var int=0` is a leftover typo from when I tried a long loop that eventually exits (sort of my own timeout instead of the browser's timeout). You can ignore it as it doesn't really change the behavior of this sample test. – Very Objective Aug 22 '18 at 06:32
  • 2
    No, it means your interviewer is wrong. – Abana Clara Aug 22 '18 at 06:34
  • That depends what you call `setTimeout`... The actual function is called synchronously (and is only "register callback to fire a *t* "). The callback will get called synchronously when currentTime > *t*, and when the js context is free. But the actual currentTime gets incremented by the machine, and is not tied by js context. So if you consider this timer increment as part of the whole `setTimeout`, then your interviewer had a point. – Kaiido Aug 22 '18 at 06:39
  • @Kaiido well then every programing language would be multi-threaded as long as the operating system which does the io handling is multi-threaded. This argument is a little too artificial. – t.niese Aug 22 '18 at 07:17
  • @t.niese sure it is a completely artificial argument, and I would laugh at an interviewer telling me this, my point was just that indeed, this function relies on other processes than the js-context. The EventLoop doesn't have to be written in js, and insome browsers (actually all but IE) UserEvents are actually registered on an other process. Might just have been a reminder that browsers are not written in js. – Kaiido Aug 22 '18 at 07:24

5 Answers5

14

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

t.niese
  • 39,256
  • 9
  • 74
  • 101
  • 1
    setTimeout will run after the main stack has cleared. Does it mean we could use it to delay computation work that's low priority? – user2734550 Nov 12 '19 at 23:42
  • Does SetTimeout waste cpu cycles while it is pausing? – David Spector Jun 22 '21 at 19:12
  • @DavidSpector what do you mean with `wast CPU cycles`. The EventLoop will check which of the pending timeouts finished in some way, which is likely done once in each iteration of the eventloop. Would you count that as wasting cpu cycles? – t.niese Jun 22 '21 at 19:44
  • It depends on how often the event loop repeats. If it is a very tight loop it could use much of the cpu's power. Also, the browser has a timing loop that setTimeout and setInterval use, and we don't know how that works either. All over the Web, the discussion focuses on how the event loop works and ignores the performance of waiting for a timing event. – David Spector Jun 23 '21 at 00:09
  • @DavidSpector that depends on the architecture the engine is running on. The event loop runs as long as the engine is running. Whether it can tell the OS/CPU to halt the engine until either IO happened or a time ran out depends on the OS. The EvenLoop could theoretically pause until the next IO or the next timer should happen. But if the OS/Architecture wouldn't support that it would need to do that in a pull-like fashion, but then any application that needs to do something like that has to do that the same way, so even then I would not call this wasting of CPU cycles. – t.niese Jun 23 '21 at 12:32
  • Not sure I understand your "pull-like" comment, but you seem to be saying that the efficiency of waiting for a timeout depends on the OS. I'd like to see a chart of this efficiency for Linux, Max, Windows, and for Firefox. Chrome, and iOS and Android, each running some standard execution load. That is a specific list, so it should be doable. Perhaps it can be measured by profiling the process that is responsible? Anyway, such a chart would be helpful in understanding the overhead of waiting for a timeout, which currently appears to be a completely unknown according to the Web. – David Spector Jun 23 '21 at 16:08
  • @DavidSpector should have been "poll" and not "pull". Anyhow, your initial comment was already an a new question. And your last comment asks for an elaborate discussion about something that has only barely to do with the initial question. That’s not how a Q&A platform works. You have to ask a new question. – t.niese Jun 24 '21 at 06:00
  • I would like to do just that, but my account is forbidden from asking any questions due to some voting down that happened many months ago. StackOverflow can be a very unfriendly place and I try to limit my comments here. I will be happy to fall silent to please you. – David Spector Jun 24 '21 at 12:19
5

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

Silvinus
  • 1,445
  • 8
  • 16
2

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.

Dmitry Surin
  • 153
  • 9
2

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.

Matt Harrison
  • 13,381
  • 6
  • 48
  • 66
  • How is this *asynchronous*? `run` runs synchronously isn't it? Sure your callback are called later on, but meanwhile your js-context was blocked, and no other event can happen. This is no different than a `while(++i < n)` Would you say this is asynchronous too? – Kaiido Aug 22 '18 at 06:55
  • The call to `defer()` is asyncronous. It returns immediately and the callback is called sometime later. The action and result are temporally separated from each other. Yes there's a blocking loop later but so is there if this was implemented outside of JavaScript. – Matt Harrison Aug 22 '18 at 06:58
  • I'm sorry, but from my reading, the first call to `defer` is made synchronously, and the other ones are also, even if triggered in `run`. If that was async, your two last logs would be called first. – Kaiido Aug 22 '18 at 07:02
  • They would run first if you move `loop.run()` down to the bottom. I said in the comment that it's blocking at that point. There's always going to be some blocking, just because you write async code doesn't mean nothing ever blocks. – Matt Harrison Aug 22 '18 at 07:04
  • The `I run first` log appears after the `I run fourth` log lexically but it still runs before it. – Matt Harrison Aug 22 '18 at 07:06
  • Well yes, async exactly means that your call is made outside of the current synchronous execution, and hence that it wont block it. And for `I run first` it's just like `b=()=>log('I run fourth'); a=()=>log('I run first'); a(); b();` this is still synchronous... – Kaiido Aug 22 '18 at 07:07
  • Under your definition of asynchronous, which is stricter than mine, I wouldn't have been able to show an example in JavaScript. Nevertheless I think my example shows a realistic illustration of what is really happening. – Matt Harrison Aug 22 '18 at 15:59
0

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

Shanaka Rusith
  • 421
  • 1
  • 4
  • 19